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!

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

2 Answers 2

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").

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' _ {} +
4
  • 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, 2015 at 21:03
  • @jbeldock, added an alternative
    – iruvar
    Jan 8, 2015 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 Jan 8, 2015 at 22:57
  • Thanks, I see the need for exec now. Just have to wrap my head around the string manipulations. :-)
    – jbeldock
    Jan 9, 2015 at 6:23

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .