7

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?

Shubham
  • 2,847
  • 4
  • 24
  • 37
  • "*When used with grep (no xargs) this works without parentheses.*" This is a little unclear. Will you please show the code? – Leon Sep 19 '16 at 20:28
  • _"the script does not exit"_ is also unclear. – Ruslan Osmanov Sep 19 '16 at 20:35
  • 2
    [`set -e` is full of gotchas](http://mywiki.wooledge.org/BashFAQ/105), to the point where the bash community is very much split on whether it's a good idea at all. – Charles Duffy Sep 19 '16 at 20:57
  • 2
    ...it would be much clearer to write your code as `git ls-files | xargs grep -I $'\r' && exit 1`, which makes your intent to terminate the script completely explicit -- both in terms of no longer being dependent on `set -e` (which changes behavior wildly between shell releases), and in terms of being obvious to the reader. – Charles Duffy Sep 19 '16 at 21:00

2 Answers2

9

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
$ 
Leon
  • 31,443
  • 4
  • 72
  • 97
1

The set -e command instructs to

Exit immediately if a command exits with a non-zero status

(see help set).

In Bash, an expression in parentheses creates a subshell (subprocess), which works like a single command. Thus, if a subshell exits with an error code, the parent script exits, too (due to the -e setting).

So if grep finds the \r character, the subshell exits with non-zero status; the main script exits with this code, too (due to set -e).

The answer to your question Why are the parentheses needed? is: because you probably want to exit the main script, if grep finds a \r character in one of the files under git control.

Ruslan Osmanov
  • 20,486
  • 7
  • 46
  • 60