Differences between revisions 8 and 10 (spanning 2 versions)
Revision 8 as of 2013-01-14 19:54:51
Size: 2569
Editor: GreyCat
Comment: More exercises! Yay!
Revision 10 as of 2014-01-21 13:43:34
Size: 2880
Editor: GreyCat
Comment: merge content that was added to BashPitfalls
Deletions are marked like this. Additions are marked like this.
Line 18: Line 18:
#!/bin/bash #!/usr/bin/env bash
Line 28: Line 28:
#!/bin/bash #!/usr/bin/env bash
Line 37: Line 37:
#!/bin/bash #!/usr/bin/env bash
Line 44: Line 44:
#!/bin/bash #!/usr/bin/env bash
Line 68: Line 68:
Even if you use `expr(1)` (which we ''do not'' recommend -- use [[ArithmeticExpression|arithmetic expressions]] instead), you still run into the same problem:

{{{#!highlight bash
set -e
foo=$(expr 1 - 1)
# The following command will not be executed:
echo survived
}}}

Why doesn't set -e (or set -o errexit, or trap ERR) do what I expected?

set -e was an attempt to add "automatic error detection" to the shell. Its goal was to cause the shell to abort any time an error occurred, so you don't have to put || exit 1 after each important command.

That goal is non-trivial, because many commands intentionally return non-zero. For example,

  if [ -d /foo ]; then ...; else ...; fi

Clearly we don't want to abort when the conditional, [ -d /foo ], returns non-zero (because the directory does not exist) -- our script wants to handle that in the else part. So the implementors decided to make a bunch of special rules, like "commands that are part of an if test are immune", or "commands in a pipeline, other than the last one, are immune".

These rules are extremely convoluted, and they still fail to catch even some remarkably simple cases. Even worse, the rules change from one Bash version to another, as Bash attempts to track the extremely slippery POSIX definition of this "feature". When a SubShell is involved, it gets worse still -- the behavior changes depending on whether Bash is invoked in POSIX mode. Another wiki has a page that covers this in more detail. Be sure to check the caveats.

Exercise for the reader: why doesn't this example print anything?

   1 #!/usr/bin/env bash
   2 set -e
   3 i=0
   4 let i++
   5 echo "i is $i"

Exercise 2: why does this one sometimes appear to work? In which versions of bash does it work, and in which versions does it fail?

   1 #!/usr/bin/env bash
   2 set -e
   3 i=0
   4 ((i++))
   5 echo "i is $i"

Exercise 3: why aren't these two scripts identical?

   1 #!/usr/bin/env bash
   2 set -e
   3 test -d nosuchdir && echo no dir
   4 echo survived

   1 #!/usr/bin/env bash
   2 set -e
   3 f() { test -d nosuchdir && echo no dir; }
   4 f
   5 echo survived

Exercise 4: why aren't these two scripts identical?

   1 set -e
   2 f() { test -d nosuchdir && echo no dir; }
   3 f
   4 echo survived

   1 set -e
   2 f() { if test -d nosuchdir; then echo no dir; fi; }
   3 f
   4 echo survived

(Answers)

Even if you use expr(1) (which we do not recommend -- use arithmetic expressions instead), you still run into the same problem:

   1 set -e
   2 foo=$(expr 1 - 1)
   3 # The following command will not be executed:
   4 echo survived

GreyCat's personal recommendation is simple: don't use set -e. Add your own error checking instead.

rking's personal recommendation is to go ahead and use set -e, but beware of possible gotchas. It has useful semantics, so to exclude it from the toolbox is to give into FUD.

BashFAQ/105 (last edited 2021-03-11 06:07:25 by dsl-66-36-156-249)