1

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

jzz11
  • 11

1 Answers1

1

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:

ilkkachu
  • 138,973