46

I would like to do something like this where on Friday, the output is for both conditions that match:

#!/bin/bash

#!/bin/bash
NOW=$(date +"%a")
case $NOW in
    Mon)
        echo "Mon";;
    Tue|Wed|Thu|Fri)
        echo "Tue|Wed|Thu|Fri";;
    Fri|Sat|Sun)
        echo "Fri|Sat|Sun";;
    *) ;;
esac

As the code above is written, the only output on Friday would be:

Tue|Wed|Thu|Fri

Desired output on Friday:

Tue|Wed|Thu|Fri
Fri|Sat|Sun

I understand that normally, only the commands corresponding to the first pattern that matches the expression are executed.

Is there a way to execute commands for additional matched patterns?

EDIT: I am not looking for fall-through behavior, but that's also a nice thing to know about. Thanks steeldriver.

7
  • 1
    Does this help? Can bash case statements cascade? Apr 3, 2020 at 0:46
  • Why not just use a series of ifs that's basically all a case statement is, just syntactic sugar to make a bunch of if or ifelse look nicer. Apr 3, 2020 at 0:47
  • @steeldriver op doesn't seem to want fall through but rather multiple match Apr 3, 2020 at 0:48
  • @user1794469 isn't that exactly what ;;& does? Apr 3, 2020 at 0:50
  • @steeldriver it totally is. I wasn't aware that's how that worked in bash. Fall through generally works by simply going to the next statement, not but testing the nest case, in other languages anyway. Apr 3, 2020 at 1:31

3 Answers 3

75

You can use the ;;& conjunction. From man bash:

               Using ;;& in place of ;; causes the shell to test
the next pattern list in the statement, if any, and execute  any
associated  list on a successful match.

Ex. given

$ cat myscript 
#!/bin/bash
NOW=$(date -d "$1" +"%a")
case $NOW in
    Mon)
        echo "Mon";;
    Tue|Wed|Thu|Fri)
        echo "Tue|Wed|Thu|Fri";;&
    Fri|Sat|Sun)
        echo "Fri|Sat|Sun";;
    *) ;;
esac

then

$ ./myscript thursday
Tue|Wed|Thu|Fri

$ ./myscript friday
Tue|Wed|Thu|Fri
Fri|Sat|Sun

$ ./myscript saturday
Fri|Sat|Sun

For more information (including equivalents in other shells) see

4
  • 11
    Today I learned!
    – filbranden
    Apr 3, 2020 at 1:20
  • So is it possible to use multiple conditions in first statement?
    – ivanzoid
    Dec 11, 2020 at 13:14
  • What an awesome feature!
    – Faither
    Jun 26, 2021 at 16:54
  • Warning that this script just work with OS settled to English Jul 8 at 14:41
1

No, the basic structure for a case statement is that only one matching segment gets executed. Except for fall-through which you are rejecting. Also, it only works on some shells, not all, and has a syntax specific for each shell. The Bash syntax for fall-through is ;;&. In ksh or zsh, the only one available ;& doesn't perform the next test, only directly execute the segment (will print both lines for all cases in Tue|Wed|Thu|Fri).

The only option for case is to split it with this two lines:

esac
case $now in

As this (Please!, keep UPPERCASE variables for environment variables):

#!/bin/bash
now=$(date +"%a")
case $now in
    Mon)
        echo "Mon";;
    Tue|Wed|Thu|Fri)
        echo "Tue|Wed|Thu|Fri";;
esac
case $now in
    Fri|Sat|Sun)
        echo "Fri|Sat|Sun";;
    *) ;;
esac
5
  • That's how I was doing it and the code is not clean or easy to maintain. I prefer the selected answer. i get that it may not be the overall best practice, but in this situation the selected answer results in code that is easier to read and maintain. For this question (i.e., my situation), it's the right answer.
    – MountainX
    Apr 5, 2020 at 19:46
  • No worries, @MountainX. If the answer helps, perfect, if it is not the best fit for your use case then also fine. Maybe at another time it could be used.
    – user232326
    Apr 6, 2020 at 3:30
  • @MountainX I would suggest this answer over the accepted one, because the logic of the code is easier to see and the code is more standard/portable. The more special-case "tricks" you have in your code, the harder it will be for anyone to maintain. Shell is tricky enough as it is without using once-in-a-lifetime syntax. In 27 years of writing shell scripts, I have never had a need for this.
    – jrw32982
    Apr 8, 2020 at 22:38
  • @jrw32982supportsMonica - good point in general. However, I already stated why I prefer the selected answer.
    – MountainX
    Apr 10, 2020 at 2:54
  • Zsh has ;| which is equivalent.
    – pxeger
    Sep 11, 2021 at 12:38
1

Another choice: use functions.

$ cat tt
#!/bin/bash

mon()     { echo "  Mon"             ; }
tue_fri() { echo "  Tue|Wed|Thu|Fri" ; }
fri_sun() { echo "  Fri|Sat|Sun"     ; }

now=$(date +"%a")
[[ $1 != "" ]] && now=$1      # use for testing

case $now in
  Mon)         mon              ;;
  Tue|Wed|Thu) tue_fri          ;;
  Fri)         tue_fri; fri_sun ;;
  Sat|Sun)     fri_sun          ;;
  *)           echo "  ERROR"   ;;
esac

and test with

$ for f in Mon Tue Wed Thu Fri Sat Sun bla ;do echo "$f"; tt "$f"; done
Mon
  Mon
Tue
  Tue|Wed|Thu|Fri
Wed
  Tue|Wed|Thu|Fri
Thu
  Tue|Wed|Thu|Fri
Fri
  Tue|Wed|Thu|Fri
  Fri|Sat|Sun
Sat
  Fri|Sat|Sun
Sun
  Fri|Sat|Sun
bla
  ERROR
1
  • 1
    IMO this is much clearer and more flexible than using ;;&. Even if it's slightly more verbose it's easy to follow and doesn't require readers understand (or even notice) this uncommon syntax.
    – dimo414
    Nov 15, 2021 at 7:01

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.