6

Why am I seeing the following:

$ find  -not -exec bash -c 'foo' \; -quit
bash: foo: command not found
$ echo $?
0

This is a reduced version of the actual script I am using which I post at the end of the question (if you really want to know).

So the question is how do I make find execute a shell using exec bash -c on a bunch of find results and exit at the first one that fails and also return a non-zero exit code that I can inspect later in my script?

* actual script *

#!/usr/bin/env bash
find path-a path-b path-c \
  -iname build.xml -not -exec bash -c 'echo -n "building {} ..." && ant -f {} build && echo "success" || (echo "failure" && exit 1)' \; -quit
RESULT=$?
echo "result was $RESULT"
1
  • I think find is not suitable for this. You can kill $PPID in the subshell, but i don't know how to do the return thing. Maybe you can combine find with some bash code, like this stackoverflow.com/questions/1116992/…
    – LatinSuD
    Sep 5, 2014 at 13:42

2 Answers 2

4

This would sort of do it:

#!/bin/bash
RESULT=0
while IFS= read -r -u3 -d $'\0' file; do
        echo -n "building $file ..."
        ant -f "$file" build &&
           echo "success" ||
           { echo "failure" ; RESULT=1 ; break; }
done 3< <(find path-a path-b path-c  -print0)

echo "result was $RESULT"

Note that it mixes find with a bash loop as showed here.

It does not use $? but instead it directly uses the variable $RESULT.

If everything goes fine $RESULT is 0, else it is 1. The loop breaks as soon as an error is encountered.

It should be hopefully safe against malicious file names (because of the use of -print0).

0

If you are the find command, you have a difficult job to do when setting your exit code. That's because you have 2 types inputs (forgetting options for the moment) with two types of jobs:

  1. The path(s) to the files you want to find. You likely would not, for example, want to exit with an error if you did find /mnt/log/storage/place -type f -mtime +7 -print. Likely there ARE files in your log storage which are younger than 7 days, so it is not an error to skip past them- that is your intent. It WOULD be an error if you could not get into the path /mnt/log/storage/place at all.
  2. The expression which controls find's actions. Elements in the expression are either true or false. For the purposes of -exec, it is true if the command it's running returns 0, false if it returns nonzero. To change the behavior of -exec would render it unique among all the other expression elements in find, and likely break a lot of existing scripts out there in the world.

So, to another solution to your question: you could do something which is similar to LatinSuD's while loop- in that you check on your command's status from a variable- but much simpler:

errorout=$(find /tmp -type f \( -exec bash -c '/tmp/doit 1>/tmp/stdoutfile' \; -o -quit \) 2>&1 )

If the script /tmp/doit is not executable or produces output to stderr, the errorout variable will get filled. Test errorout and react like:

[ -n "$errorout" ] || { echo 'Oh no- an error from find!'; /do/something/else; }

For a one-liner, you could do this:

errorout=$(find /tmp -type f \( -exec bash -c '/bin/false || { echo "error" 1>&2; exit 1; }' \; -o -quit \) 2>&1 )

Replace /bin/false with a command of your choosing. You can execute a command list like this:

errorout=$(find /tmp -type f \( -exec bash -c '{ command1 && command2 && command3; } || { echo "error" 1>&2; exit 1; }' \; -o -quit \) 2>&1 )

And if you didn't want to exit immediately upon the first failure of a command in your list:

errorout=$(find /tmp -type f \( -exec bash -c '{ command1; command2; command3; } || { echo "error" 1>&2; exit 1; }' \; -o -quit \) 2>&1 )

...don't forget the semicolons before the closing braces in the command strings.

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.