3

I wanted to make a list of similar clear commands clearer to read, so I've made a little terminal loop

for what in \
   cache \
   thumbs \
; do my template $what:clear; done

It works great, however I want to achieve an equivalent of

my template cache:clear && my template thumbs:clear

Is there a way how to easily and clearly put && to work in such loop, without the complexity of adding if/else statement to the loop body checking for last command exit code and calling break if not 0?

If condition is enaugh for you:

for what in \
   cache \
   thumbs \
; do
my template $what:clear;
if [ $? -ne 0 ]; then break; fi \
; done
5

You could use set -e:

(set -e;
for what in \
   cache \
   thumbs;
do my template $what:clear; done)

set -e causes the shell to exit when a command exits with a non-zero exit code, and the failure isn’t handled in some other way. In the above, the whole subshell exits if my ... fails, and the subshell’s exit code is the failed command’s exit code. This works in shell scripts and on the command line.

Another approach is to use a disjunction:

for what in \
   cache \
   thumbs; do
    my template $what:clear || break
done

This has the disadvantage of requiring || break after every command, and of swallowing the exit code indicating failure; the for loop will exit with a zero exit code every time. You can avoid this by making break fail, which will cause the for loop to exit with an exit code of 1 (which is still not as good as the subshell approach above):

for what in \
   cache \
   thumbs; do
    my template $what:clear || break -1 2>/dev/null
done

(break -1 causes break to exit with a non-zero exit code, and 2>/dev/null throws away its error message).

As far as I know you can’t use && to exit a loop, as in && done or something similar.

  • @StephenKitt && done or something similar. yes I was thinking exactly about something like that - something that would scream "I'm checking for every command exit code !" – jave.web Sep 23 '19 at 16:31
  • @TobySpeight confirmed, that works (no () wrap = terminal will close :) ), it would be nice for others to have a little more description of this in the answer :) , however me personally - I will go with || break , since that seems like the clearest alternative to && (it would be nice to note that this simple || break works as well and you dont have to redirect to devnull) – jave.web Sep 23 '19 at 16:40
3

There's no direct way to exit a loop if an error occurs. The obvious way is to use a while loop instead of a for loop, but that doesn't combine easily with iterating over a list. You'd need to assemble the list into a variable and then explicitly remove the elements from the list, which makes the code a lot more complex.

items=(cache thumbs);
while ((${#items[@]})) && my template "${items[0]}":clear; do shift items; done
((${#items[@]} == 0)) # to check if all the items were processed

A shorter way to write this than using an if statement is

for what in cache thumbs; do
  my template "$what":clear || break;
done

Using boolean operators is usually less readable than using if, but somecommand || fallback is fairly idiomatic.

Note that the for loop always succeeds. There's no convenient way to know whether the code exited early (you can test whether $what is the last item after the loop, but that requires having a convenient way to know what the last item was and it doesn't let you know whether the last item was processed successfully).

This doesn't scale well to multiple commands. You can instead turn on errexit mode with set -e. This causes the shell to exit as soon as a command fails (in simple cases — the details are rather complex, but it's enough here). Note that the whole (sub)shell exits, not just the containing loop or the containing function or the containing script. To exit just a part of the script, put that part in a subshell. Note that variable modifications don't survive the subshell.

( set -e;
  for what in cache thumbs; do
    my template "$what":clear;
  done
)
# Here $? is 0 if all the mv commands succeeded and nonzero otherwise

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.