18

Python has a handy language feature called "for-else" (similarly, "while-else"), which looks like this:

for obj in my_list:
    if obj == target:
        break
else: # note: this else is attached to the for, not the if
    print "nothing matched", target, "in the list"

Essentially, the else is skipped if the loop breaks, but runs if the loop exited via a condition failure (for while) or the end of iteration (for for).

Is there a way to do this in bash? The closest I can think of is to use a flag variable:

flag=false
for i in x y z; do
    if [ condition $i ]; then
        flag=true
        break
    fi
done
if ! $flag; then
    echo "nothing in the list fulfilled the condition"
fi

which is rather more verbose.

nneonneo
  • 171,345
  • 36
  • 312
  • 383

6 Answers6

10

You could put a sentinel value in the loop list:

for i in x y z 'end-of-loop'; do
    if [ condition $i ]; then
        # loop code goes here
        break
    fi
    if [ $i == 'end-of-loop' ]; then
        # your else code goes here
    fi
done
Paul Evans
  • 27,315
  • 3
  • 37
  • 54
10

Using a subshell:

( for i in x y z; do
    [ condition $i ] && echo "Condition $i true" && exit;
done ) && echo "Found a match" || echo "Didn't find a match"
nneonneo
  • 171,345
  • 36
  • 312
  • 383
devnull
  • 118,548
  • 33
  • 236
  • 227
  • 2
    Short and sweet. I like it. – nneonneo Aug 27 '13 at 15:43
  • 3
    Big BUG! The list of `x y z` never gets to `y z` if `x` is false! @nneonneo how could you accept that?! – Robert Siemer Jan 08 '15 at 16:39
  • 2
    Here's what I'm using: `( for i in x y z ; do [ condition $i ] && echo "Condition $i true" && exit; done ) && echo "Found a match" || echo "Didn't find a match"`. Note the use of `&& exit` instead of `|| exit`, which is key to make it continue when `x` is false. – nneonneo Mar 15 '17 at 07:33
  • 1
    Hope you don't mind if I fixed the code to match what I asked for. – nneonneo Dec 18 '17 at 22:21
9

Something very hacky to introduce similar syntax:

#!/bin/bash

shopt -s expand_aliases

alias for='_broken=0; for'
alias break='{ _broken=1; break; }'
alias forelse='done; while ((_broken==0)); do _broken=1;'

for x in a b c; do
        [ "$x" = "$1" ] && break
forelse
        echo "nothing matched"
done

 

$ ./t.sh a
$ ./t.sh d
nothing matched
Adrian Frühwirth
  • 42,970
  • 10
  • 60
  • 71
  • 1
    Cool. Aliases can be helpful sometimes. – konsolebox Aug 27 '13 at 15:16
  • 1
    @konsolebox I agree, aliases are way underrated. Even though they are themselves limited they help work around other limitations where they are the only (or prettiest) solution. I used this technique before to implement shell exception handling and while I am sure many people find this ugly and idiotic I think it's rather beautiful in a way. – Adrian Frühwirth Aug 27 '13 at 15:33
  • 1
    It is actually an interesting use of aliases. Thank you for the solution (+1). – nneonneo Aug 27 '13 at 15:43
  • +1. Very interesting. (Reminds one of macros in C to mimic Pascal style.) – devnull Aug 27 '13 at 17:02
  • You probably don't need '==0' in the forelse alias, though. – devnull Aug 27 '13 at 17:04
  • @devnull True, you could e.g. use an `until` loop instead and skip the `==0` or just invert the toggle variable :-) – Adrian Frühwirth Aug 27 '13 at 18:33
3

You can do this but I personally find it hard to read:

while :;
  do for i in x y z; do
    if [[ condition ]]; then
      # do something
      break 2
  done
  echo Nothing matched the condition
  break
done
rici
  • 234,347
  • 28
  • 237
  • 341
2

I also enjoy devnull's answer, but this is even more pythonic:

for i in x y z; do
  [ condition $i ] && break #and do stuff prior to break maybe?
done || echo "nothing matched"

This will only echo "nothing matched" if the loop did not break.

srblum
  • 21
  • 3
1

You can change this

if ! $flag; then
    echo "nothing in the list fulfilled the condition"
fi

to something simpler like this

"$flag" || echo "nothing in the list fulfilled the condition"

if you only have one statement after it, although that's not really going to help much.

konsolebox
  • 72,135
  • 12
  • 99
  • 105