15

I was trying to write a script that looped through each xml file in a directory and run make NAME= where NM was the filename minus the .xml, the place where I got stuck was assigning the {} placeholder to a variable. As

find . -iname "*.xml" -exec foo=$(echo {}); gmake NAME=$FOO \;

does not work as nothing is assigned to $FOO.

1
  • I could have sworn I have done something like this before and it worked: find . -exec TMP=1; echo $TMP \; But this morning this gives the error "find: missing argument to `-exec' ;" and I'm baffled what the heck I did that worked and why this is giving such an unexpected error! I know I have done this lots of times even with a pipe and now everything I try is an error. Even find . -exec TMP=1 \; gives errors!
    – Michael
    Dec 9, 2018 at 16:11

2 Answers 2

15

After much searching on IRC someone pointed me to the following answer

find . -iname "*.xml" -exec bash -c 'echo "$1"' bash {} \;

or for my example (with the string cut removed to save confusion)

find . -iname "*.xml" -exec bash -c 'gmake NAME="$1"' bash {} \;

The way this works is bash takes the parameters after -c as arguments, bash {} is needed so that the contents of {} is assigned to $1 not $0, and bash is used to fill in $0. It's not only a placeholder as the contents of that $0 is used in error messages for instance so you don't want to use things like _ or ''

To process more that one file per invocation of bash, you can do:

find . -iname "*.xml" -exec bash -c '
   ret=0
   for file do
       gmake NAME="$file" || ret=$?
   done
   exit "$ret"' bash {} +

That one has the added benefit that if any of the gmake invocations fails, it will reported in find's exit status.

More info can be taken from http://mywiki.wooledge.org/UsingFind#Complex_actions

7
  • _ for example won't work in sh? Which sh?
    – muru
    Nov 19, 2015 at 5:19
  • I have run across this problem before. find's handling of argument substitution is pretty weak. Passing to a shell script (inline or otherwise) seems to be the only way to really get a handle on the arguments.
    – user235504
    Nov 19, 2015 at 8:20
  • _ is a bash predefined variable (not defined in dash for instance): "At shell startup, set to the absolute pathname used to invoke the shell or shell script being executed as passed in the environment or argument list." Nov 19, 2015 at 9:19
  • @muru the strict implementation of /bin/sh from what i was reading as opposed to most distro's that now don't ship it and just symlink something else like bash or dash
    – simotek
    Nov 19, 2015 at 11:04
  • 1
    @muru In the Bourne shell, the _ is passed as $1. But apart from /bin/sh on Solaris (as opposed to /bin/ksh or sh which should be /usr/bin/xpg4/sh or /usr/bin/xpg6/sh) and for some 20-year-old servers running Unix variants that haven't received a security update in a decade, there are no Bourne shells anymore. sh today means a POSIX shell, and these all pass _ as $0. Nov 19, 2015 at 23:22
-3

Simotek's answer is awesome! Changed the format so that I can rename a HUGE amount of files in a one-liner...

find * -name "*.sh" -exec bash -c 'OLD=$1; NEW=$(echo $OLD | sed s/OLD-NAME.sh/NEW_NAME.sh/g); mv "$OLD" "$NEW"' _ {} \;
1
  • 3
    Your code would fail on filenames contain filename globbing characters and names tha start with a dash. It also seems to assume that all found names are OLD-NAME.sh. I don't quite know why you use /g at the end of the sed expression, or why you're using sed at all here as you would get the effect by just doing NEW=NEW_NAME.sh, assuming the old name was OLD-NAME.sh that is. You may want to fix your code so that it's actually testable and does what it says it does.
    – Kusalananda
    Jul 21, 2021 at 20:18

You must log in to answer this question.

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