Colton Leekley-Winslow Colton Leekley-Winslow - 2 months ago 8
Bash Question

Bash "set -e" and negated return code

I have a shell script which checks for windows line endings.

set -e

(! git ls-files | xargs grep -I $'\r')


I am using the "!" character to negate the return code of the command. Grep will return code 0 when a file with carriage return is found, and "!" negates the value to the return code is then 1 and the script exits. When used with grep (no xargs) this works without parentheses. When xargs is used the negation takes place according to $?, as "echo $?" will print "1", however the script does not exit! After adding parentheses around the whole command, it works as expected. Why are the parentheses needed?

Answer

Your problem has nothing to do with xargs.

The -e option of bash is a little tricky.

-e Exit immediately if a pipeline (which may consist of a single simple command), a list, or a compound command , exits with a non-zero status. The shell does not exit if the command that fails is part of the command list immediately following a while or until keyword, part of the test following the if or elif reserved words, part of any command executed in a && or || list except the command following the final && or ||, any command in a pipeline but the last, or if the command's return value is being inverted with !.

Let's look at a much simpler example:

$ cat exit_on_error_test.sh 
#!/bin/bash

trap 'echo Interrupted because of an error' ERR
set -e

! true
echo Exit status: $?

$ ./exit_on_error_test.sh 
Exit status: 1
$

So, even though the exit status of "! true" was non-zero, the script was allowed to run to the end and output the value of the exit status. That's because we didn't have any failing command - the non-zero exit code was due to deliberate negation.

However, if we enclose "! true" in parentheses we introduce a failing (compound) command.

$ cat exit_on_error_test.sh 
#!/bin/bash

trap 'echo Interrupted because of an error' ERR
set -e

(! true) # This as a whole is now a failing (compound) command
echo Exit status: $?

$ ./exit_on_error_test.sh 
Interrupted because of an error
$