I'm using "find" to rename files.
This works: find . -iname a* -exec bash -c 'x={};mv {} ${x/a/b}' \;
but this doesn't:
find . -iname a* -exec bash -c "x={};mv {} ${x/a/b}" \;
Why?
Running on Bash.
Thanks
I'm using "find" to rename files.
This works: find . -iname a* -exec bash -c 'x={};mv {} ${x/a/b}' \;
but this doesn't:
find . -iname a* -exec bash -c "x={};mv {} ${x/a/b}" \;
Why?
Running on Bash.
Thanks
First, see: What is the difference between the "...", '...', $'...', and $"..." quotes in the shell?
This works:
find . -iname a* -exec bash -c 'x={};mv {} ${x/a/b}' \;
If you don't have any filenames in the current directory that start with an a
, and if any of the filenames in the tree don't have whitespace or characters special to the shell, then yes, perhaps it'll work.
but this doesn't:
find . -iname a* -exec bash -c "x={};mv {} ${x/a/b}" \;
The "x={};mv {} ${x/a/b}"
part is double-quoted, so the shell expands the ${...}
right there, before running the command. find
will see just the resulting string, probably x={};mv {}
, since $x
isn't likely to be set in the outer shell.
In the first one, the single quotes prevent expansions, so find
sees x={};mv {} ${x/a/b}
, substitutes the filename for {}
and passes the result to the shell you asked it to run. It might just work.
However, there are a few problems with that. The first one being the unquoted -iname a*
. That will expand the glob in the shell, before running find
, so if your directory contains e.g. abcd.txt
and asdf.txt
, it'll be the same as running find . -iname abcd.txt asdf.txt
. The first filename goes with -iname
, and the second is an error.
Then, the substitution of {}
. Consider a filename like foo bar.txt
, found somewhere in the tree. find
changes x={};mv {} ${x/a/b}
to x=foo bar.txt;mv foo bar.txt ${x/a/b}
and runs it in the shell. But that's not the syntax you wanted, that'll assign foo
to x
, and run bar.txt
as a command. Mixing code and data like this can hardly ever work, especially with filenames that can contain any characters. This also opens an obvious command injection vulnerability, e.g. with a filename like foo; echo doing bad things
.
The proper way to do that would be to pass the filenames as positional parameters to the inner shell, and to quote the expansions in the script.
So,
find . -iname "a*" -exec bash -c 'mv "$1" "${1/a/b}"' sh {} \;
See:
{}
in the shell code. – Kamil Maciorowski Nov 04 '22 at 11:23${x/a/b}
gets expanded by the shell instead of getting passed tofind
and get expanded by theexec
command. – Philippos Nov 04 '22 at 11:27