Parameter expansion not working in "find -exec" - bash

I'm writing a bash script as above, but the parameter expansion is not working with the EXC variable.
#!/bin/bash
EXC="--exclude='*.js' --exclude='*.sh'"
find /path -exec grep ${EXC} "xxx" {} \; >> result.txt
Options in the EXC variable are not used by the grep call as it still parse JavaScript files...
Also tried
find /path -exec grep $EXC "xxx" {} \; >> result.txt

You can filter find results with ! (not) operator combined with -name option. To exclude .js and .sh files:
find . -type f ! -name '*.js' ! -name '*.sh' -exec grep xxx {} \;

The problem is that the single quotes are not removed from the parameter expansion, so grep is receiving '*.js' as the pattern, not *.js as you want. You need to use an array to hold the arguments:
exc=( "--exclude=*.js" "--exclude=*.sh" ) # No single quotes needed
find /path -exec grep "${exc[#]}" "xxx" {} \; >> result.txt
The quoted parameter expansion prevents *.js and *.sh from being expanded by the shell in the same way that quoting them when used literally does. --exclude='*.js' and '--exclude=*.js' would both result in the same argument being passed to grep, as would the minimalist --exclude=\*.js. You could also define your array as
exc=( --exclude "*.js" --exclude "*.sh" )
since the long option and its argument can be specified as one word containing = or as two separate words.

It still parses or it's only applied by find also on files that you are trying to exclude?
Anyway, why not use only grep -r instead of find plus grep?
grep -r --exclude='*.js' --exclude='*.sh' "xxx" /path

The problem is that i'm using a variable to hold some arguments, and parameter expansion does not replace the ${} thing by its value in the command line...
You can launch a subshell wich will perform what you want
#!/bin/bash
EXC="--exclude='*.js' --exclude='*.sh'"
find /path -exec bash -c "grep ${EXC} 'xxx' {}" \; >> result.txt

these could help you;
#!/bin/bash
EXC="--exclude='*.js' --exclude='*.sh'"
find /path -exec echo "grep ${EXC} 'xxx' {}" \; | bash > result.txt
or
#!/bin/bash
EXC="*js,*sh"
find /path -exec echo "grep --exclude={${EXC}} 'xxx' {}" \; | bash > result.txt

Related

I used the'-print0' option to handle filenames with spaces, but I get an error

#!/bin/bash
find /home/data -name '*QQ*' -print0 -exec bash -c ' mv $1 ${0/\-QQ/-TT}' {} \;
I used the'-print0' option to handle filenames with spaces, but I get an error
/home/data/gone to sea.1080p-QQ.mp4mv: target 'sea.1080p-TT.mp4' is not a directory
Which part is wrong?
Thanks
You don't need -print0, since you're not piping the output to another program.
You just need to quote properly in the bash command.
find /home/data -name '*-QQ*' -exec bash -c 'mv "$1" "${1/\-QQ/-TT}"' {} {} \;
This should work as long as the filenames don't contain double quote or $ characters.
You could also avoid bash -c by using the rename command:
find /home/data -name '*-QQ*' -exec rename 's/-QQ/-TT/' {} +

find -exec when used with sed for file rename not working

I've been trying:
find dev-other -name '*.flac' -type f -exec echo $(echo {} | sed 's,^[^/]*/,,') \;
I expect to see a list of paths to .flac files within dev-other, but without a prepended dev-other/, e.g.:
4515/11057/4515-11057-0095.flac
4515/11057/4515-11057-0083.flac
4515/11057/4515-11057-0040.flac
4515/11057/4515-11057-0105.flac
4515/11057/4515-11057-0017.flac
4515/11057/4515-11057-0001.flac
Instead I see
dev-other/4515/11057/4515-11057-0095.flac
dev-other/4515/11057/4515-11057-0083.flac
dev-other/4515/11057/4515-11057-0040.flac
dev-other/4515/11057/4515-11057-0105.flac
dev-other/4515/11057/4515-11057-0017.flac
Why isn't the sed replace working here even though it works on its own
$ echo $(echo dev-other/4515/11057/4515-11057-0047.flac | sed 's,^[^/]*/,,')
4515/11057/4515-11057-0047.flac
I first tried with expansions:
find dev-other -name '*.flac' -type f -exec a={} echo ${a#*/} \;
But got the errors:
find: a=dev-other/700/122866/700-122866-0001.flac: No such file or directory
find: a=dev-other/700/122866/700-122866-0030.flac: No such file or directory
find: a=dev-other/700/122866/700-122866-0026.flac: No such file or directory
find: a=dev-other/700/122866/700-122866-0006.flac: No such file or directory
find: a=dev-other/700/122866/700-122866-0010.flac: No such file or directory
You can just use parameter expansion for your use-case when using find with the -exec option,
find dev-other -name '*.flac' -type f -exec bash -c 'x=$1; y="${x#*/}"; echo "$y"' bash {} \;
I used a separate shell (use bash or sh) using bash -c because to involve separate string operations involving parameter expansion. Think of each output of find result to be passed as argument to this sub-shell where this manipulation takes place.
When bash -c executes a command, the next argument after the command is used as $0 (the script's "name" in the process listing), and subsequent arguments become the positional parameters ($1, $2, etc.). This means that the filename passed by find (in place of the {}) becomes the first parameter of the script -- and is referenced by $1 inside the mini-script
If you don't want to use an extra bash, use _ in-place
find dev-other -name '*.flac' -type f -exec bash -c 'x=$1; y="${x#*/}"; echo "$y"' _ {} \;
where _ i is a bash predefined variable (not defined in dash for instance): "At shell startup, set to the absolute path-name used to invoke the shell or shell script being executed as passed in the environment or argument list" ( See man bash - Special Parameters section)
Worth looking at Using Find - Complex Actions

Edit a find -exec echo command to include a grep for a string

So I have the following command which looks for a series of files and appends three lines to the end of everything found. Works as expected.
find /directory/ -name "file.php" -type f -exec sh -c "echo -e 'string1\string2\nstring3\n' >> {}" \;
What I need to do is also look for any instance of string1, string2, or string3 in the find ouput of file.php prior to echoing/appending the lines so I don't append a file unnecessarily. (This is being run in a crontab)
Using | grep -v "string" after the find breaks the -exec command.
How would I go about accomplishing my goal?
Thanks in advance!
That -exec command isn't safe for strings with spaces.
You want something like this instead (assuming finding any of the strings is reason not to add any of the strings).
find /directory/ -name "file.php" -type f -exec sh -c "grep -q 'string1|string2|string3' \"\$1\" || echo -e 'string1\nstring2\nstring3\n' >> \"\$1\"" - {} \;
To explain the safety issue.
find places {} in the command it runs as a single argument but when you splat that into a double-quoted string you lose that benefit.
So instead of doing that you pass the file as an argument to the shell and then use the positional arguments in the shell command with quotes.
The command above simply chains the echo to a failure from grep to accomplish the goal.

Use current filename ("{}") multiple times in "find -exec"?

Many sources say that every instance of {} will be replaced with the filename found through find, but when I try to run the following, I only get one text file and its name is ".txt"
find /directory -name "*pattern*" -exec cut -f8 {} > {}.txt \;
The goal was to create a text file with only the eighth column from each file found, and each text file will be named after its parent file. Something about that second set of {} is not replacing with the filename of each found file.
Try:
find /directory -name "*pattern*" -exec sh -c 'cut -f8 {} > {}.txt' \;
But be aware that some versions of find require {} to be a distinct argument, and will not expand {} to a filename otherwise. You can work around that with:
find /directory -name "*pattern*" -exec sh -c 'cut -f8 $0 > $0.txt' {} \;
(this alternate command will put the output file in the subdirectory which contains the matched file. If desired, you could avoid that by redirecting to ${0#*/}
The issue is that find is not doing the redirection, the shell is. Your command is exactly equivalent to:
# Sample of INCORRECT code
find /directory -name "*pattern*" -exec cut -f8 {} \; > {}.txt
Note the following from the standard:
If more than one argument containing only the two characters "{}" is present, the behavior is unspecified.
If a utility_name or argument string contains the two characters "{}" , but not just the two characters "{}" , it is implementation-defined whether find replaces those two characters or uses the string without change.
To deal with the caveats that William Pursell mentioned in his answer, use the following:
find /directory -name "*pattern*" -exec sh -c 'cut -f8 "$1" > "$1.txt"' x {} \;
When you use sh -c, it gets the positional parameters from arguments following the string to execute. The extra x fills in $0, and the substituted filename will become $1.
The double quotes allow this to work properly with filenames containing spaces and other special characters.
find /directory -name "*pattern*" | xargs awk '{z=FILENAME".txt";print $8>z}'

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