2997
Comment: Exercise 5
|
9832
Add link to set -e behavior reference
|
Deletions are marked like this. | Additions are marked like this. |
Line 10: | Line 10: |
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". |
Clearly we don't want to abort when the `[ -d /foo ]` command 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". |
Line 14: | Line 13: |
[[https://www.in-ulm.de/~mascheck/various/set-e/|A reference comparing behavior across various historical shells]] also exists. Consider this allegory, originally posted to [[https://lists.gnu.org/archive/html/bug-bash/2017-03/msg00171.html|bug-bash]]: {{{#!highlight text numbers=off Once upon a time, a man with a dirty lab coat and long, uncombed hair showed up at the town police station, demanding to see the chief of police. "I've done it!" he exclaimed. "I've built the perfect criminal-catching robot!" The police chief was skeptical, but decided that it might be worth the time to see what the man had invented. Also, he secretly thought, it might be a somewhat unwise move to completely alienate the mad scientist and his army of hunter robots. So, the man explained to the police chief how his invention could tell the difference between a criminal and law-abiding citizen using a series of heuristics. "It's especially good at spotting recently escaped prisoners!" he said. "Guaranteed non-lethal restraints!" Frowning and increasingly skeptical, the police chief nevertheless allowed the man to demonstrate one robot for a week. They decided that the robot should patrol around the jail. Sure enough, there was a jailbreak a few days later, and an inmate digging up through the ground outside of the prison facility was grabbed by the robot and carried back inside the prison. The surprised police chief allowed the robot to patrol a wider area. The next day, the chief received an angry call from the zookeeper. It seems the robot had cut through the bars of one of the animal cages, grabbed the animal, and delivered it to the prison. The chief confronted the robot's inventor, who asked what animal it was. "A zebra," replied the police chief. The man slapped his head and exclaimed, "Curses! It was fooled by the black and white stripes! I shall have to recalibrate!" And so the man set about rewriting the robot's code. Black and white stripes would indicate an escaped inmate UNLESS the inmate had more than two legs. Then it should be left alone. The robot was redeployed with the updated code, and seemed to be operating well enough for a few days. Then on Saturday, a mob of children in soccer clothing, followed by their parents, descended on the police station. After the chaos subsided, the chief was told that the robot had absconded with the referee right in the middle of a soccer game. Scowling, the chief reported this to the scientist, who performed a second calibration. Black and white stripes would indicate an escaped inmate UNLESS the inmate had more than two legs OR had a whistle on a necklace. Despite the second calibration, the police chief declared that the robot would no longer be allowed to operate in his town. However, the news of the robot had spread, and requests from many larger cities were pouring in. The inventor made dozens more robots, and shipped them off to eager police stations around the nation. Every time a robot grabbed something that wasn't an escaped inmate, the scientist was consulted, and the robot was recalibrated. Unfortunately, the inventor was just one man, and he didn't have the time or the resources to recalibrate EVERY robot whenever one of them went awry. The robot in Shangri-La was recalibrated not to grab a grave-digger working on a cold winter night while wearing a ski mask, and the robot in Xanadu was recalibrated not to capture a black and white television set that showed a movie about a prison break, and so on. But the robot in Xanadu would still grab grave-diggers with ski masks (which it turns out was not common due to Xanadu's warmer climate), and the robot in Shangri-La was still a menace to old televisions (of which there were very few, the people of Shangri-La being on the average more wealthy than those of Xanadu). So, after a few years, there were different revisions of the criminal-catching robot in most of the major cities. In some places, a clever criminal could avoid capture by wearing a whistle on a string around the neck. In others, one would be well-advised not to wear orange clothing in certain rural areas, no matter how close to the Harvest Festival it was, unless one also wore the traditional black triangular eye-paint of the Pumpkin King. Many people thought, "This is lunacy!" But others thought the robots did more good than harm, all things considered, and so in some places the robots are used, while in other places they are shunned. The end.}}} |
|
Line 24: | Line 109: |
Line 34: | Line 118: |
Line 36: | Line 119: |
Line 42: | Line 126: |
Line 50: | Line 133: |
Line 52: | Line 134: |
Line 58: | Line 141: |
Line 65: | Line 147: |
Line 67: | Line 148: |
Line 71: | Line 153: |
([[/Answers|Answers]]) |
([[BashFAQ/105/Answers|Answers]]) |
Line 82: | Line 163: |
Subshells from command substitution unset `set -e`, however (unless `inherit_errexit` is set with Bash 4.4): {{{#!highlight bash set -e foo=$(expr 1 - 1; true) # Will run: echo survived }}} Note that set -e is '''not''' unset for commands that are run asynchronously, for example with process substitution: {{{#!highlight bash set -e mapfile foo < <(true; echo foo) echo ${foo[-1]} # foo mapfile foo < <(false; echo foo) echo ${foo[-1]} # bash: foo: bad array subscript }}} Another pitfall associated with `set -e` occurs when you use commands that ''look like'' assignments but aren't, such as `export`, `declare`, `typeset` or `local`. {{{#!highlight bash set -e f() { local var=$(somecommand that fails); } f # will not exit g() { local var; var=$(somecommand that fails); } g # will exit }}} In function `f`, the exit status of `somecommand` is discarded. It won't trigger the `set -e` because the exit status of `local` masks it (the assignment to the variable succeeds, so `local` returns status 0). In function `g`, the `set -e` is triggered because it uses a ''real'' assignment which returns the exit status of `somecommand`. Using [[ProcessSubstitution|Process substitution]], the exit code is also discarded as it is not visible from the main script: {{{#!highlight bash set -e cat <(somecommand that fails) echo survived }}} Using a pipe makes no difference, as only the ''rightmost'' process is considered: {{{#!highlight bash set -e somecommand that fails | cat - echo survived }}} `set -o pipefail` is a workaround by returning the exit code of the ''first'' failed process: {{{#!highlight bash set -e -o pipefail failcmd1 | failcmd2 | cat - # The following command will not be executed: echo survived }}} though with pipefail in effect, code like this will sometimes cause an error, depending on whether the output of somecmd exceeds the size of the pipe buffer or not: {{{#!highlight bash set -e -o pipefail somecmd | head -n1 # The following command will sometimes be executed, depending on how much output somecmd writes: echo survived }}} |
|
Line 86: | Line 225: |
geirha's personal recommendation is to handle errors properly and not rely on the unreliable `set -e`. |
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 [ -d /foo ] command 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.
A reference comparing behavior across various historical shells also exists.
Consider this allegory, originally posted to bug-bash:
Once upon a time, a man with a dirty lab coat and long, uncombed hair
showed up at the town police station, demanding to see the chief of
police. "I've done it!" he exclaimed. "I've built the perfect
criminal-catching robot!"
The police chief was skeptical, but decided that it might be worth
the time to see what the man had invented. Also, he secretly thought,
it might be a somewhat unwise move to completely alienate the mad
scientist and his army of hunter robots.
So, the man explained to the police chief how his invention could tell
the difference between a criminal and law-abiding citizen using a
series of heuristics. "It's especially good at spotting recently
escaped prisoners!" he said. "Guaranteed non-lethal restraints!"
Frowning and increasingly skeptical, the police chief nevertheless
allowed the man to demonstrate one robot for a week. They decided that
the robot should patrol around the jail. Sure enough, there was a
jailbreak a few days later, and an inmate digging up through the
ground outside of the prison facility was grabbed by the robot and
carried back inside the prison.
The surprised police chief allowed the robot to patrol a wider area.
The next day, the chief received an angry call from the zookeeper.
It seems the robot had cut through the bars of one of the animal cages,
grabbed the animal, and delivered it to the prison.
The chief confronted the robot's inventor, who asked what animal it
was. "A zebra," replied the police chief. The man slapped his head and
exclaimed, "Curses! It was fooled by the black and white stripes!
I shall have to recalibrate!" And so the man set about rewriting the
robot's code. Black and white stripes would indicate an escaped
inmate UNLESS the inmate had more than two legs. Then it should be
left alone.
The robot was redeployed with the updated code, and seemed to be
operating well enough for a few days. Then on Saturday, a mob of
children in soccer clothing, followed by their parents, descended
on the police station. After the chaos subsided, the chief was told
that the robot had absconded with the referee right in the middle of
a soccer game.
Scowling, the chief reported this to the scientist, who performed a
second calibration. Black and white stripes would indicate an escaped
inmate UNLESS the inmate had more than two legs OR had a whistle on
a necklace.
Despite the second calibration, the police chief declared that the robot
would no longer be allowed to operate in his town. However, the news
of the robot had spread, and requests from many larger cities were
pouring in. The inventor made dozens more robots, and shipped them off
to eager police stations around the nation. Every time a robot grabbed
something that wasn't an escaped inmate, the scientist was consulted,
and the robot was recalibrated.
Unfortunately, the inventor was just one man, and he didn't have the
time or the resources to recalibrate EVERY robot whenever one of them
went awry. The robot in Shangri-La was recalibrated not to grab a
grave-digger working on a cold winter night while wearing a ski mask,
and the robot in Xanadu was recalibrated not to capture a black and
white television set that showed a movie about a prison break, and so
on. But the robot in Xanadu would still grab grave-diggers with ski
masks (which it turns out was not common due to Xanadu's warmer climate),
and the robot in Shangri-La was still a menace to old televisions (of
which there were very few, the people of Shangri-La being on the average
more wealthy than those of Xanadu).
So, after a few years, there were different revisions of the
criminal-catching robot in most of the major cities. In some places,
a clever criminal could avoid capture by wearing a whistle on a string
around the neck. In others, one would be well-advised not to wear orange
clothing in certain rural areas, no matter how close to the Harvest
Festival it was, unless one also wore the traditional black triangular
eye-paint of the Pumpkin King.
Many people thought, "This is lunacy!" But others thought the robots
did more good than harm, all things considered, and so in some places
the robots are used, while in other places they are shunned.
The end.
Exercise for the reader: why doesn't this example print anything?
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?
Exercise 3: why aren't these two scripts identical?
Exercise 4: why aren't these two scripts identical?
Exercise 5: under what conditions will this fail?
(Answers)
Even if you use expr(1) (which we do not recommend -- use arithmetic expressions instead), you still run into the same problem:
Subshells from command substitution unset set -e, however (unless inherit_errexit is set with Bash 4.4):
Note that set -e is not unset for commands that are run asynchronously, for example with process substitution:
Another pitfall associated with set -e occurs when you use commands that look like assignments but aren't, such as export, declare, typeset or local.
In function f, the exit status of somecommand is discarded. It won't trigger the set -e because the exit status of local masks it (the assignment to the variable succeeds, so local returns status 0). In function g, the set -e is triggered because it uses a real assignment which returns the exit status of somecommand.
Using Process substitution, the exit code is also discarded as it is not visible from the main script:
Using a pipe makes no difference, as only the rightmost process is considered:
set -o pipefail is a workaround by returning the exit code of the first failed process:
though with pipefail in effect, code like this will sometimes cause an error, depending on whether the output of somecmd exceeds the size of the pipe buffer or not:
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.
geirha's personal recommendation is to handle errors properly and not rely on the unreliable set -e.