Mark Rogers wrote:
The only problem I had turned out to be a single directory whose name started with |, but that was easy enough to deal with manually. However, I'd be interested to know how it could be generalised to avoid that being a problem (mainly from a security point of view; not handling the directory was fine, but I'm sure someone could work out an exploit and I'm just curious as to how to avoid it in the general case).
You need to be careful about what interprets the string, and how; in this case the shell interpreted it, and treats it as a pipe symbol. To avoid that in general you need to be careful about using appropriate quoting/escaping, and it's very easy to get wrong, especially when you start passing strings between program invocations in shell scripts, read from files containing filenames etc.
For example, here I've added single quotes around the {}:
$ mkdir formark2 $ cd formark2 $ mkdir '|bardir' $ touch --date="1 year ago" '|bardir/old' $ find . -mindepth 1 -type d -exec /bin/sh -c "echo checking '{}'; echo '{}' >> delme; fi" ; $ cat delme ./|bardir $ xargs --interactive -n 1 rm -r < delme rm -r ./|bardir ?...yes
What happens here is that the double quotes are interpreted by the shell you're using to invoke find. So the find program will be invoked with 10 arguments, with the last-but-one being the text between the double quotes. It then replaces the {} occurrences with the current filename, so you end up with:
echo checking './|bardir'; echo './|bardir' >> delme
and it then invokes /bin/sh passing that as an argument. Because the pipe symbol is protected by those single quotes, it is not interpreted as a pipe command. Great! Except... this doesn't work if you have a file with a single quote:
$ mkdir "./single'quote" $ find . -mindepth 1 -type d -exec /bin/sh -c "echo checking '{}'" ; /bin/sh: Syntax error: Unterminated quoted string
because now you end up with:
echo checking './single'quote'
which is not legal syntax. You can work around that by using double quotes, but then you have a problem with filenames containing double quotes or dollar signs. Really you want to escape all meta characters, but then it rapidly becomes complex as you pass this through multiple levels of interpretation.
You're much better off avoid this problems: don't invoke shell commands with filenames or other unsafe input. For example:
$ find . -mindepth 1 -type d -print0 | xargs -0 -n 1 echo ./single'quote ./|bardir
What happens here is that your filename is written to stdout (terminated with a null character), read by xargs in the same way, and then passed as an argument to echo during an exec system call; it never gets near a shell.
And if you find yourself doing non-trivial logic (like in your example), use a scripting language where you just pass the string around different parts of the program in a variable, and eventually to some system call. Mind you, scripting languages have their own string meta characters and interpolation behaviour with associated quoting/escaping mechanisms, so you still need to be careful.
-- Martijn