GNU find -exec Exit Status: When a Failed Command Still Returns 0
An empty report fails test -s, yet GNU find -exec test -s '{}' \; can still exit with status 0. Switch to the batched {} + form and a failed batch makes find exit nonzero. A following -print can still list both reports.
To see why, track three results separately. The child command exits with a status, the -exec action evaluates to true or false for a path, and find returns its own status to the calling shell.

Make an empty report and a nonempty one
Use /bin/sh on Linux and check that find --version identifies GNU findutils. Paste this setup into the shell. Once it prints the new temporary directory, use the same session for the examples.
lab=$(mktemp -d /tmp/find-exec.XXXXXXXX) &&
(
cd "$lab" &&
mkdir samples &&
printf 'ready\n' > 'samples/ready report.txt' &&
: > 'samples/empty report.txt'
) &&
printf 'Lab directory: %s\n' "$lab"
Define this helper in the same shell. It runs each example in the lab directory and captures stdout, stderr, and the exit status separately. It sorts the saved lines for display because traversal order can vary.
run() (
cd "${lab:?Run the lab setup first}" || exit
export LC_ALL=C
label=$1
shift
status=0
"$@" > out 2> err || status=$?
printf '\n%s status=%s\n' "$label" "$status"
sed 's/^/stdout: /' out | sort
sed 's/^/stderr: /' err | sort
)
Each status= line shows what find returned. The helper saves that number before piping the captured text through sort.
With \; the child decides whether the path passes
run per-file find samples -type f -exec test -s '{}' \; -print
per-file status=0
stdout: samples/ready report.txt
The backslash passes the semicolon through to find, where it ends the action. find runs test -s once for each regular file. The nonempty report gives test status 0, so the action is true and evaluation reaches -print. The empty report gives test status 1, so the action is false and the implicit AND skips -print. Both checks are quiet on stderr. Since the search itself completed without errors, find exits with 0. POSIX find specification.
That makes the semicolon form useful when you want a child command to select files. Negate the action to print the ones that fail the size check. See how implicit AND and negation work in a find expression.
run inverted-per-file find samples -type f ! -exec test -s '{}' \; -print
inverted-per-file status=0
stdout: samples/empty report.txt
With + a failed batch changes the final status
run batched find samples -type f \
-exec sh -c 'for file do test -s "$file" || exit 7; done' check '{}' + -print
batched status=1
stdout: samples/empty report.txt
stdout: samples/ready report.txt
The child shell deliberately exits with 7 as soon as a file fails the size check. GNU find records the failed batch and exits with 1 in this run. The documented guarantee is a nonzero status, so test for nonzero in a script. GNU find manual on the -exec action.
Both paths reach -print because the batched action is always true in the expression. It queues paths for a later invocation, whose exit status affects the final status of find. The two printed paths tell you which files reached -print.
In the shell command, check occupies $0 and for file do loops over the remaining arguments. Each path is one argument, including its embedded space. See the argument-by-argument walkthrough in find -exec vs xargs.
Report every empty file and fail the batch
To report every empty file, keep the loop running after a failed check. Print the path to stderr, set failed=1, and use that flag as the child shell's exit status when the loop finishes.
run report-all find samples -type f -exec sh -c '
failed=0
for file do
if ! test -s "$file"; then
printf "empty: %s\n" "$file" >&2
failed=1
fi
done
exit "$failed"
' check '{}' +
report-all status=1
stderr: empty: samples/empty report.txt
The check is for nonzero file size. A report with incorrect contents can still pass. In a larger tree, find can start several shell invocations. Each checks its own arguments, and one failed batch is enough for find to exit nonzero. In a script, drop run report-all and use the find command directly as the condition of an if.
| What you need | How to get it |
|---|---|
| Print paths whose check passes | Use -exec … {} \; followed by -print. |
| Print paths whose check fails | Use ! -exec … {} \; followed by -print. |
| Report failing paths and signal failure to a script | Use -exec … {} + with a wrapper that reports each failed check and exits nonzero for the batch. |
Predict stdout, stderr, and the status
Keep the two-file lab as it is. For each command, predict both output streams and the status shown by run.
-
The batch shell prints
ranand exits with7, but the search asks for CSV files. What do you see?run zero-matches find samples -type f -name '*.csv' \ -exec sh -c 'printf "ran\n"; exit 7' check '{}' + -
The fail-fast batch from earlier now receives only the report beginning with
ready. What changes?run all-good find samples -type f -name 'ready*' \ -exec sh -c 'for file do test -s "$file" || exit 7; done' check '{}' + -print -
The semicolon form starts from
absent, a path the lab never created. Where does the failure come from?run missing-root find absent -type f -exec test -s '{}' \; -print
Answers
-
Both streams are empty and the status is
0. No path reached the batch action, so the child shell never ran.zero-matches status=0 -
The one selected path is printed, stderr is empty, and the status is
0. The nonempty file passes the size check and the shell finishes successfully.all-good status=0 stdout: samples/ready report.txt -
Stdout is empty and
findwrites its own diagnostic to stderr. The missing starting path makes the search fail before any child runs. Herefindexits with1. GNU find manual on exit status.missing-root status=1 stderr: find: 'absent': No such file or directory
AI assisted with research, drafting, editing, lab execution, and the practice questions. The illustration is AI-generated. The seven command variants were run with GNU findutils 4.10.0 and /bin/sh on aarch64 Linux in an Alpine 3.24.4 Docker container.