2

Shortcutting: (expr1 && expr2), rather than an if ... fi statement. If expr1, expr2,... are to be generated by a loop, can you do it?

Here's a toy example:

$ for cond in {0,0,0}; do if [[ $cond = 0 ]]; then echo 0; else echo 1; break; fi; done && echo "true" || echo "false"
0
0
0
true

$ for cond in {0,1,0}; do if [[ $cond = 0 ]]; then echo 0; else echo 1; break; fi; done && echo "true" || echo "false"
0
1
true

How would I change the if...fi statement such that the first returns only true, and the second false? If I replace echo 1 by exit within, well, the terminal window shuts off.

4
  • You don't have all of the elements of the for loop inside the for loop; can you elaborate what you're actually trying to do?
    – Jeff Schaller
    Commented Jul 18, 2019 at 0:03
  • What elements of the for loop are missing?
    – Erwann
    Commented Jul 18, 2019 at 0:08
  • Sorry for the miscommunication; my interpretation is that you want to summarize the for loop by testing each value for 0; since the if statement inside the for loop only sees one value at a time, it cannot shortcut. Unless you want to exit the loop whenever you see a "false" value (such as 1 here)?
    – Jeff Schaller
    Commented Jul 18, 2019 at 0:14
  • What it boils down to I think is how to I make the loop break and return status false when it encounters $cond = 0? Such that it can be chained with && to the next command? I think I got it: $ (for cond in {0,1,0}; do if [[ $cond = 0 ]]; then true; else false; exit; fi; done) && echo "true" || echo "false" false PS: from here: stackoverflow.com/questions/14059342/…
    – Erwann
    Commented Jul 18, 2019 at 0:46

3 Answers 3

1

The exit status of the loop is that of the last command executed there, which is either echo or break, and both usually have a exit status of 0. So you need to preserve and check the status of the condition. I'd suggest using a function:

loopcheck() {
for cond in {0,1,0}; do
  if [[ $cond = 0 ]]; then
    echo 0; 
  else
    ret=$?;   # preserve exit status of test
    echo 1; 
    return $ret;  # pass it on
  fi;
done
}

loopcheck && echo "true" || echo "false"

Another option is saving $ret like above and checking its value outside the loop.

0

I take this over the other answer (however useful) because it is closer to the requirement implicit in the question:

$ (for cond in {0,1,0}; do if [[ $cond = 0 ]]; then true; else false; exit; fi; done) && echo "true" || echo "false" false 
0

The for name in ... loop relays the exit status of the last command executed.

When you break a loop, the break command itself has an exit status of 0 by default, which is therefore relayed by the for, thus making your && test after the done be equal to true.

One way to work around that is as you said in your self-answer. That is a nice and clean one, and works well with any POSIX compliant shell.

But it has one drawback in that, by being a sub-shell, it doesn't fully share its Execution Environment1 with its "outer" shell. It does inherit it on beginning execution, but does not "propagate" it back to its "outer" parent on finishing execution, meaning for instance that any variable set or modified within the loop won't be seen after outside the loop.

Speaking for Bash, there is an alternative: you can avoid the sub-shell and still use break while forcing it to fail by passing it a -1 argument, because -1 is always an invalid number of loops to quit. Bash will display an error message for that, but you can mute it by redirecting to 2>/dev/null.

However note also that this behavior is not properly POSIX compliant, because break is considered a special built-in, and a "special built-in" incurring into error should exit a non-interactive shell (e.g. a script).

1[...]. Changes made to the subshell environment cannot affect the shell’s execution environment. [...]

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .