Shell – How to store a string literal to a variable with no substitution or interpretation

shellvariable

To flatten a directory structure, I can do this:

find . -type f -exec sh -c 'mv "{}" "./`basename "{}"`"'  \;

I want to store the following in my profile as $FLATTEN

-exec sh -c 'mv "{}" "./`basename "{}"`"'  \;

so that later I can just execute find . $FLATTEN

I'm having trouble storing the variable because it gets interpreted too early. I want it to be stored as a string literal and interpreted only in usage on the shell, not when sourced.

Best Answer

If using GNU mv, you should rather do:

find . -type f -exec mv -t . {} +

With other mvs:

find . -type f -exec sh -c 'exec mv "$@" .' sh {} +

You should never embed {} in the sh code. That's a command injection vulnerability as the names of the files are interpreted as shell code (try with a file called `reboot` for instance).

Good point for quoting the command substitution, but because you used the archaic form (`...` as opposed to $(...)), you'd need to escape the inner double quotes or it won't work in sh implementations based on the Bourne shell or AT&T ksh (where "`basename "foo bar"`" would actually be treated as "`basename " (with an unmatched ` which is accepted in those shells) concatenated with foo and then bar"`").

Also, when you do:

mv foo/bar bar

If bar actually existed and was a directory, that would actually be a mv foo/bar bar/bar. mv -t . foo/bar or mv foo/bar . don't have that issue.

Now, to store those several arguments (-exec, sh, -c, exec mv "$@" ., sh, {}, +) into a variable, you'd need an array variable. Shells supporting arrays are (t)csh, ksh, bash, zsh, rc, es, yash, fish.

And to be able to use that variable as just $FLATTEN (as opposed to "${FLATTEN[@]}" in ksh/bash/yash or $FLATTEN:q in (t)csh), you'd need a shell with a sane array implementation: rc, es or fish. Also zsh here as it happens none of those arguments is empty.

In rc/es/zsh:

FLATTEN=(-exec sh -c 'exec mv "$@" .' sh '{}' +)

In fish:

set FLATTEN -exec sh -c 'exec mv "$@" .' sh '{}' +

Then you can use:

find . -type f $FLATTEN