Iterating over associative array in bash - bash

I am renaming strings recursively using an associative array. Th array part is working, when I echo $index & ${code_names[$index]}they print correctly. However the files are not modified. When I run the find | sed command in the shell it works however inside a bash script it doesnt.
Update
Also script runs ok if I just hardcode the string to be renamed: find . -name $file_type -print0 | xargs -0 sed -i 's/TEST/BAT/g'
#!/usr/bin/env bash
dir=$(pwd)
base=$dir"/../../new/repo"
file_type="*Kconfig"
cd $base
declare -A code_names
code_names[TEST]=BAT
code_names[FUGT]=BLANK
for index in "${!code_names[#]}"
do
find . -name $file_type -print0 | xargs -0 sed -i 's/$index/${code_names[$index]}/g'
done

The bare variable $file_type gets expanded by the shell. Double quote it. Variables are not expanded in single quotes, use double quotes instead. Note that it can break if $index or ${code_names[$index]} contain characters with special meaning for sed (like /).
find . -name "$file_type" -print0 \
| xargs -0 sed -i "s/$index/${code_names[$index]}/g"

Related

Bash Recursively Remove Leading Underscore

I'm attempting to recursively remove a leading underscore from some scss files. Here's what I have.
find . -name '*.scss' -print0 | xargs -0 -n1 bash -c 'mv "$0" `echo $0 | sed -e 's:^_*::'`'
When I'm in a specific directory this works perfectly:
for FILE in *.scss; do mv $FILE `echo $FILE | sed -e 's:^_*::'`; done
What am I doing wrong in the find?
As the starting point is ., all paths that find prints start with a dot. Thus ^_* doesn't match anything and sed returns its input unchanged.
I wouldn't bother with sed or xargs though.
The script below works with any find and sh that isn't terribly broken, and properly handles filenames with underscores in the middle as well.
find . -name '_*.scss' -exec sh -c '
for fp; do # pathname
fn=${fp##*/} # filename
fn=${fn#"${fn%%[!_]*}"} # filename w/o leading underscore(s)
echo mv "$fp" "${fp%/*}/$fn"
done' sh {} +
A less portable but shorter and much cleaner alternative in bash looks like:
shopt -s globstar extglob nullglob
for fp in ./**/_*.scss; do
echo mv "$fp" "${fp%/*}/${fp##*/+(_)}"
done
Drop echo if the output looks good.
Look at the syntax highlighting of the sed command. Single quotes can't be nested. Easiest fix: switch to double quotes.
find . -name '*.scss' -print0 | xargs -0 -n1 bash -c 'mv "$0" `echo $0 | sed -e "s:^_*::"`'
I would recommend leaving $0 as the program name and using $1 for the first argument. That way if bash prints an error message it'll prefix it with bash:.
find . -name '*.scss' -print0 | xargs -0 -n1 bash -c 'mv "$1" `echo "$1" | sed -e "s:^_*::"`' bash
You can also simplify this with find -exec.
find . -name '*.scss' -exec bash -c 'mv "$1" `echo "$1" | sed -e "s:^_*::"`' bash {} ';'
You could also let bash do the substitution with its ${var#prefix} prefix removal syntax:
find . -name '*.scss' -exec bash -c 'mv "$1" "${1#_}"' bash {} ';'
You don't need find at all (unless you are trying to do this with an ancient version of bash, such as what macOS ships):
shopt -s globstar extglob
for f in **/*.scss; do
mv -- "$f" "${f##*(_)}"
done
${f##...} expands f, minus the longest prefix matching .... The extended pattern *(_) matches 0 or more _, analogous to the regular expression _*.

grep cannot read filename after find folders with spaces

Hi after I find the files and enclose their name with double quotes with the following command:
FILES=$(find . -type f -not -path "./.git/*" -exec echo -n '"{}" ' \; | tr '\n' ' ')
I do a for loop to grep a certain word inside each file that matches find:
for f in $FILES; do grep -Eq '(GNU)' $f; done
but grep complains about each entry that it cannot find file or directory:
grep: "./test/test.c": No such file or directory
see picture:
whereas echo $FILES produces:
"./.DS_Store" "./.gitignore" "./add_license.sh" "./ads.add_lcs.log" "./lcs_gplv2" "./lcs_mit" "./LICENSE" "./new test/test.js" "./README.md" "./sxs.add_lcs.log" "./test/test.c" "./test/test.h" "./test/test.js" "./test/test.m" "./test/test.py" "./test/test.pyc"
EDIT
found the answer here. works perfectly!
The issue is that your array contains filenames surrounded by literal " quotes.
But worse, find's -exec cmd {} \; executes cmd separately for each file which can be inefficient. As mentioned by #TomFenech in the comments, you can use -exec cmd {} + to search as many files within a single cmd invocation as possible.
A better approach for recursive search is usually to let find output filenames to search, and pipe its results to xargs in order to grep inside as many filenames together as possible. Use -print0 and -0 respectively to correctly support filenames with spaces and other separators, by splitting results by a null character instead - this way you don't need quotes, reducing possibility of bugs.
Something like this:
find . -type f -not -path './.git/*' -print0 | xargs -0 egrep '(GNU)'
However in your question you had grep -q in a loop, so I suspect you may be looking for an error status (found/not found) for each file? If so, you could use -l instead of -q to make grep list matching filenames, and then pipe/send that output to where you need the results.
find . -print0 | xargs -0 egrep -l pattern > matching_filenames
Also note that grep -E (or egrep) uses extended regular expressions, which means parentheses create a regex group. If you want to search for files containing (GNU) (with the parentheses) use grep -F or fgrep instead, which treats the pattern as a string literal.

Function in /etc/bashrc is not passing quotes through variables

I am trying to write a bashrc function to add a safety check to the find command when it is paired with "-exec rm". This script cuts off everything beginning with "-exec rm" and replaces it with a "-print0" which is then passed over to sed to re-add missing quotes if the user uses a quoted experssion.
I am running into a issue where the quotes I am adding via sed are not being passed to the line with the execution of the find command.
Rewriting the find command and re-adding quotes:
FIND_VAR=$(echo "$#" | sed "s/-exec rm.*/-print0/g" | sed 's/\*.* / "&" /g' | sed 's/ "/"/g')
Running the find command with the modifications:
FIND_LIST=$(/bin/find $FIND_VAR | sed 's|\./| \./|g')
What I would like to accomplish is if the user types the following:
find ./ -type f -name "*.txt" -exec rm -rf {} \;
The command is re-written via the bashrc to run as:
find ./ -type f -name "*.txt" -print0 | sed 's|\./| \./|g'
This generates a list of files each spaced out which is passed to a modified rm function:
./file1.txt ./file2.txt ./file3.txt ...
The full function is listed below for reference:
function find () {
if echo "$#" | grep -q '-exec rm' ; then
echo "Found rm command as part of find"
FIND_VAR=$(echo "$#" | sed "s/-exec rm.*/-print0/g" | sed 's/\*.* / "&" /g' | sed 's/ "/"/g')
echo "/bin/find $FIND_VAR"
FIND_LIST=$(/bin/find $FIND_VAR | sed 's|\./| \./|g')
echo "$FIND_LIST"
echo "rm$FIND_LIST"
else
/bin/find "$#"
fi
}
Don't put a command in a string. It will not work. See http://mywiki.wooledge.org/BashFAQ/050 for more details and discussion.
You have the command in an array. Use it.
Walk the array, find the arguments you want to remove and remove them then add the arguments you want to add to the array and run the new command.

How can bash interpret a zero-length argument from an expanded string?

On OSX, running sed to edit in place works by passing a zero-length argument like this:
find . -name "*.java" -print | xargs sed -f src/main/scripts/remove_snippets.sed -i ""
However, putting the -i "" into a shell variable does not work:
dashi='-i ""'
find . -name *.java -print | xargs sed -f src/main/scripts/remove_snippets.sed $dashi
Instead of editing in place the "" gets interpreted as a literal string to use for the backup extension, leaving a directory of java files named *.java"".
How can bash be told to interpret the "" as an empty argument rather than an argument containing two double-quotes?
Use an array.
dashi=(-i "")
find . -name *.java -print | xargs sed -f src/main/scripts/remove_snippets.sed "${dashi[#]}"

execute shell command with variable

To execute 'find' with some variables from txt file i made this
but it doesn't work.
is that wrong with execute statement?
#/bin/bash
while read line;
do
echo tmp_name: $line
for ST in 'service.getFile("'$line;
do
find ./compact/ -type f -exec grep -l $ST {} \;
done
done < tmpNameList.txt
Try and quote $ST in your find command.
What's more:
since you operate from the current directory, ./ is not necessary;
you don't seem to have any special regex character (the ( needs to be quoted in grep's classical regex mode, and I assume you did mean a literal dot), so use fgrep instead (or grep -F). Ie:
find compact/ -type f -exec fgrep -l "$ST" {} \;
grep can read multiple patterns from a file (-f option):
find ./compact/ -type f -exec grep -f patterns.txt {} +
where patterns.txt (prepend 'service.getFile(' to each line) is:
sed 's/^/service.getFile(/' tmpNameList.txt >patterns.txt

Resources