2

I'm struggling with escaping and expression evaluation. I am trying to run a command inside a find...-exec COMMAND structure, where COMMAND operates on one file and outputs to the stdout. To use this COMMAND normally on the command line, I simply redirect to a new file, like this:

COMMAND inputfilename.md > outputfilename.md.conf

This works fine. However, if I want to recursively loop over ALL the files in a directory and its subdirectory, using find -exec, I can't seem to figure out how to separate the file name and the path so that I can redirect properly. For example, this produces an error:

find ./ *.md -not -path './/.git/*' -exec COMMAND '{} > ~/wiki/newdirectory/{}.cong' \;

because {} expands the entire file path. I've tried various combinations of basename and dirname, but I can't seem to get them to evaluate (instead, I just get the string 'basename' etc.). For example, this didn't work, because the text 'basename' just shows up

find ./ *.md -not -path './/.git/*' -exec COMMAND  '{} > ~/wiki/newdirectory/''basename {}''.conf' \;

(returns this error: 'No such file or directory - .//newdirectory/README.md.conf > ~/wiki/newdirectory/basename .//newdirectory/README.md.conf').

Any help would be appreciated. To summarize, the objective is:

  1. Recursively iterate over all files matching *.md in a directory
  2. Execute COMMAND inputfile.md > ~/newdirectory/inputfile.md.conf where inputfile is the same string in each iteration.

Thanks!

  • Unrelated: Instead of -not -path, you should use -prune. – muru Jan 8 '15 at 21:06
  • Also, does it have to be directly -exec? Can you use xargs or bash -c '...'? – muru Jan 8 '15 at 21:07
5

Whatever you do, you'll need to invoke a shell to perform the redirection of the command output to a file whose location depends on the find result.

find ./ *.md -not -path './/.git/*' -exec sh -c 'COMMAND "$0" > ~/wiki/newdirectory/"${0##*/}.cong"' {} \;

Don't substitute {} inside the shell script. This isn't supported on all systems, and even where it is, that would not work in general, since it would treat the file name as a piece of shell syntax, e.g. a file called ;rm -rf ~;.md would cause you to erase all your files.

${0##*/} uses pure string manipulation to obtain the base name of the file. You could also use $(basename -- "$0").

1

If your find accepts -execdir, this should work

find . -name '*.md' -not -path './/.git/*' -execdir COMMAND {} > ~/wiki/newdirectory/{}.cong \;

Alternately

find . -name '*.md' -not -path './/.git/*' -exec bash -c \
'for f; do COMMAND "$f" > ~/wiki/newdirectory/"${f##*/}".cong; done' _ {} +
  • Unfortunately, find is not actually executing inside each file's respective directory with -execdir. This appears possibly to be a known bug in OS X find. I'm poking at it now. – jbeldock Jan 8 '15 at 21:03
  • @jbeldock, added an alternative – iruvar Jan 8 '15 at 21:28
  • The first command can't work this way: you need to invoke a shell to perform the redirection. What you wrote redirects the output of the find command and all the subprocesses it launches to a file named ~/wiki/newdirectory/{}.cong (with the braces in the file name). @jbeldock – Gilles Jan 8 '15 at 22:57
  • Thanks, I see the need for exec now. Just have to wrap my head around the string manipulations. :-) – jbeldock Jan 9 '15 at 6:23

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.