2
#!/bin/bash -eu

items="1 2"
for item in $items; do
(
    echo one
    false
    echo two
) ||:
done

I wish false line to break a subshell, but continue processing the outer loop. I.e. the expected output is

one
one

however, I get

one
two
one
two

as if ||: stands exactly after false line, not after the subshell.

Can anyone explain, why does this happen?

Ixanezis
  • 1,631
  • 13
  • 20
  • As I understand the COMMAND EXECUTION ENVIRONMENT section of the `bash` man page, only subshells spawned by command substitutions (`$(...)`) inherit the `-e` option. – chepner May 16 '13 at 13:00
  • 1
    I don't think so, if remove `||:` at the end, the script fails successfully on `false` line. – Ixanezis May 16 '13 at 15:39
  • Good point. However, here's another question: when the subshell does exit, presumably it exits with non-zero status due to the false, in which case: why does the shell executing the `for`-loop not exit as well? In other words, why does "one" print twice? – chepner May 16 '13 at 16:29
  • Didn't get you. "one" printed twice is what I'm trying to achieve, if you remove `||:` at the end, the script will fail both in subshell and in outer shell, printing "one" once. – Ixanezis May 16 '13 at 20:42
  • I would think that with `set -e`, the entire script would exit if the subshell exited, so you would see `one` printed *once*, the script would exit, and the loop would never execute the second time. – chepner May 16 '13 at 20:44
  • With `set -e` at the very beginning of a script, yes, it only prints `one` once and exits immediately... Is that what you were asking about? >_ – Ixanezis May 16 '13 at 21:15
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/30094/discussion-between-chepner-and-ixanezis) – chepner May 16 '13 at 21:30

1 Answers1

2

Set -e inside the subshell and remove the ||::

#!/bin/bash -u

items="1 2"
for item in $items; do
(
    set -e
    echo one
    false
    echo two
)
done

Another seemingly working approach is:

#!/bin/bash -eu

items="1 2"
for item in $items; do
(
    echo one
    false
    echo two
) | awk '{print}'
done

I suppose the reason why your approach doesn't work is the following (quoted from man bash):

The shell does not exit if the command that fails is [...] part of any command executed in a && or || list except the command following the final && or ||

Ansgar Wiechers
  • 193,178
  • 25
  • 254
  • 328
  • Because of the `-e`, I suppose. – glglgl May 16 '13 at 11:45
  • @AnsgarWiechers, thanks for informing, then there's no need for same thing to be repeated :) I removed my answer – abasu May 16 '13 at 12:42
  • 1
    So why doesn't it work in my implementation? Looks like a hack to add a `set +e` right before the loop, `set -e` in a subshell and another `set -e` after the loop. – Ixanezis May 16 '13 at 14:50
  • @AnsgarWiechers To me, that would seem to apply only to a command that is *directly* part of the `&&` or `||` list. However, given that the behavior is identical in versions 3.x and 4.x, and your quote from the version 4 man page is more explicit and closer to the observed behavior than the text in the version 3 man page, I think this is correct. – chepner May 16 '13 at 16:15
  • @AnsgarWiechers Looks like you're right. I knew that section of bash you're quoting.. Just couldn't realize that `false` line from inside the subshell is actually a part of long command, followed by `||`. Thanks for your answer, though I'm still looking for a nicer solution, cause your second one 1) also fails with `set -o pipefail` turned on and 2) from the first glance someone would ask "What is this awk '{print}' doing here..?" – Ixanezis May 16 '13 at 16:29
  • I have to admit that this looks like the best solution of all. It is discussed [here](http://unix.stackexchange.com/questions/65532/why-does-set-e-not-work-inside) and [here](http://austingroupbugs.net/view.php?id=52). The only thing I'd change is replacing `awk '{print}'` with `cat`. – Ixanezis May 17 '13 at 10:42