80

In popular imperative languages, switch statements generally "fall through" to the next level once a case statement has been matched.

Example:

int a = 2;
switch(a)
{
   case 1:
      print "quick ";
   case 2: 
      print "brown ";
   case 3: 
      print "fox ";
      break;
   case 4:
      print "jumped ";
}

would print "brown fox".

However the same code in bash

A=2
case $A in
2)
  echo "QUICK"
  ;&
2)
  echo "BROWN"
  ;&
3)
  echo "FOX"
  ;&
4)
  echo "JUMPED"
  ;&
esac

only prints "BROWN"

How do I make the case statement in bash "fall through" to the remaining conditions like the first example?

(edit: Bash version 3.2.25, the ;& statement (from wiki) results in a syntax error)

running:

test.sh:

#!/bin/bash
A=2
case $A in
1)
  echo "QUICK"
  ;&
2)
  echo "BROWN"
  ;&
3)
  echo "FOX"
  ;&
esac

Gives:

./test.sh: line 6: syntax error near unexpected token ;' ./test.sh:
line 6:
;&'

Resorath
  • 1,532
  • 1
  • 12
  • 30
  • 2
    Related (but not duplicate) question: [Switch case with fallthrough?](http://stackoverflow.com/q/5562253) (regarding the `|` operator for separating multiple patterns in a `case` expression). – TachyonVortex Jul 02 '15 at 09:41

4 Answers4

55

The ;& and ;;& operators were introduced in bash 4.0, so if you want to stick with a five year old version of bash, you'll either have to repeat code, or use ifs.

if (( a == 1)); then echo quick; fi
if (( a > 0 && a <= 2)); then echo brown; fi 
if (( a > 0 && a <= 3)); then echo fox; fi
if (( a == 4)); then echo jumped; fi

or find some other way to achieve the actual goal.

(On a side note, don't use all uppercase variable names. You risk overwriting special shell variables or environment variables.)

Community
  • 1
  • 1
geirha
  • 5,403
  • 1
  • 28
  • 35
  • Wow! Thank you, I see now my 'new' MacBookPro 2012 has a very old 2007 Bash: GNU bash, version 3.2.48(1)-release (x86_64-apple-darwin12) – AnneTheAgile Dec 07 '13 at 18:07
  • 6
    @AnneTheAgile, yes. bash 3.2 is GPLv2, bash 4.0 (and newer) is GPLv3, and Apple "doesn't like" GPLv3 (http://en.wikipedia.org/wiki/GPL_v3#Legal_Barrier_to_App_Stores). Though you can easily get a recent bash with homebrew or macports or similar, of course. – geirha Dec 09 '13 at 10:02
  • @geirha: Oh wow. googling for this question, and read this answer and your comment. As a non-macOS user, question, are things any different ~four years on? – i336_ May 29 '17 at 14:50
  • @i336_ nope, MacOs is still shipping with bash 3.2 – geirha May 29 '17 at 18:18
  • Uppercase for globals is a good style to follow, then use lowercases for `local`s. – Will Jun 09 '17 at 01:21
  • 2
    @Will not in bash and sh, since shell variables share the namespace with environment variables and special shell variables. – geirha Jun 10 '17 at 05:32
  • Well, you just need to name variables descriptively and avoid namespace conflicts. – Will Jun 19 '17 at 04:04
  • Pay no attention to the version snobs: a lot of new unix people think newer/shinier is always better (and obviously have never flown or had surgery), and especially may not understand that enterprise products stay on branches derived from releases and don't get feature updates. – user2066657 Aug 13 '18 at 20:40
  • 1
    @Will, part of "avoid namespace conflicts" is staying out of the namespace that POSIX specifies for use for variables meaningful to the operating system and shell. That namespace is defined by the standard as the set of all-caps names; see https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html, keeping in mind that environment and shell variables share a namespace. – Charles Duffy Jul 24 '19 at 20:04
  • @MestreLion "12 years old" and it's still working well. Hate to toss it just because it's stale like a michelangelo . But, I work in Enterprise, so it's different. – user2066657 Apr 21 '21 at 00:44
  • @user2066657: 12 is for Bash 4, **15** for 3.2. But to the point: the reason Apple blocks 4.0 is **not** because it's less "enterprisey" than 3.2, it's because of GPL3. It has nothing to do with stability, maturity or security. It's _not_ a technical decision, please stop implying so. – MestreLion Apr 21 '21 at 07:10
  • @user2066657: on the contrary: by adopting legacy software you transfer all the maintenance burden to yourself. You miss all security patches, all CVE reports, you have to do everything yourself. True, Apple is big enough to have a dedicated Bash team, but it's still a huge (duplicated) effort for no technical gain. And by enforcing a "no-GPL3" policy you alienate your users from all the benefits of a modern shell out of the box. – MestreLion Apr 21 '21 at 07:26
  • @MestreLion - You sound confused. Of course one uses software supported by an enterprise where one specifically does not take on the support burden. That's the entire reason for staying with established and well-tested versions: because that's what the enterprise distro vendors do to ease their version churn. It was that way when I worked for a distro and it's that way with (eg RedHat) now. You do know that's what they do and why they do it .. right? – user2066657 Apr 27 '21 at 05:13
52

Try this:

case $VAR in
normal)
    echo "This doesn't do fallthrough"
    ;;
fallthrough)
    echo -n "This does "
    ;&
somethingelse)
    echo "fall-through"
    ;;
esac
dimo414
  • 44,897
  • 17
  • 143
  • 228
René Steetskamp
  • 863
  • 7
  • 12
13

Using ;& is not very portable, as it requires bash (not ash, dash, or any other minimal sh) and it requires at least bash 4.0 or newer (not available on all systems, e.g. macOS 10.14.6 still only offers bash 3.2.57).

A work around that I consider much nicer to read than a lot of if's is loop and modify the case var:

#!/bin/sh

A=2
A_BAK=$A
while [ -n "$A" ]; do
    case $A in
        1)
            echo "QUICK"
            A=2
            ;;

        2)
            echo "BROWN"
            A=3
            ;;

        3)
            echo "FOX"
            A=4
            ;;

        4)
            echo "JUMPED"
            A=""
            ;;
    esac
done
A=$A_BAK

Here's a proof of concept: https://www.onlinegdb.com/0ngLPXXn8

Mecki
  • 114,800
  • 31
  • 226
  • 239
  • 2
    "real `sh`" is what, pre-POSIX (aka 1970s-era-syntax) Bourne? – Charles Duffy Jul 24 '19 at 20:06
  • 4
    @CharlesDuffy `/bin/sh` is a Bourne shell, the common base for BASH, DASH, ASH and also for the POSIX standard. Each of these shells support additional features, some unique to the shell but they are all SH compatible, unlike some other shells (csh and tcsh are not, zsh is only partially) – Mecki Jul 27 '19 at 15:34
  • 5
    Bourne is a shell from the 1970s. POSIX sh is a specification from 1991. `/bin/sh` on modern systems is POSIX, **not** Bourne. To pick an easy-to-test-for difference, `echo hello ^ cat` will emit `hello` on Bourne, because `^` is a pipe character there; whereas in a POSIX-compliant shell, it emits `hello ^ cat` as output, because the `^` is parsed as an argument to `echo`. – Charles Duffy Jul 27 '19 at 15:35
  • ...if you're looking for a Bourne implementation you can build and run today, [the Heirloom project maintains one](http://heirloom.sourceforge.net/sh.html) (based on the same codebase as a non-POSIX-compliant `/bin/sh` that SunOS shipped into the early 2000s). `ash` and `dash`, by contrast, are POSIX-compliant in all the places where POSIX and original Bourne differ. – Charles Duffy Jul 27 '19 at 15:37
  • 2
    (Incidentally, the `^`-as-a-pipe-character difference is the one that GNU autoconf uses to distinguish whether it's on Bourne or POSIX sh; that said, the POSIX spec generally took a lot of inspiration from early ksh, and thus codified behaviors that were originally ksh extensions). – Charles Duffy Jul 27 '19 at 15:41
  • @CharlesDuffy The Bourne shell was written 1977 and replaced the PWB shell 1979 in UNIX Version 7. The PWB shell was an extension of the Thompson shell and the Thompson shell in Version 4 already introduced `|` to replace `^` in Verson 4, so something about your facts is wrong here as the Bourne shell was written to be compatible to the Thompson shell, so something about your facts is wrong here. https://en.wikipedia.org/wiki/Thompson_shell - https://en.wikipedia.org/wiki/Bourne_shell – Mecki Jul 27 '19 at 15:48
  • In SunOS and Heirloom Bourne implementations (the recent surviving variants), *both* `|` and `^` are accepted as pipe characters, so whatever documentation is telling you that Bourne doesn't accept `^` as a pipe is simply wrong. If you want to test it yourself, log into the freenode IRC network, and `/msg evalbot b# echo hello ^ cat`, compared to `/msg evalbot # echo hello ^ cat`. – Charles Duffy Jul 27 '19 at 15:53
  • 4
    Definitely valid answer. Many embedded machines ship with ash and even sh in ubuntu is dash. For those shells this answer is valid. Have no idea why this is downvoted and why are we arguing whether non bash, sh is relevant...It is even quite elegant and idiomatic – Paulo Neves Dec 10 '19 at 16:16
  • This response doesn't answer OP's question about how to achieve fall-through. – Cameron Hudson Dec 20 '21 at 20:27
  • @CameronHudson It does fall through. I jump to 2 and it falls through to 3 and 4. I could also jump to 3 and it would just fall through to 4. And if I jump to 1, it falls through all cases. `A=""` simulates a break, so it won't fall through beyond that point. If I add two more cases below, 5 and 6, I can make 5 fall through to 6. Here's the proof, just run it https://onlinegdb.com/0ngLPXXn8 – Mecki Dec 20 '21 at 21:20
-2

bash switch with fallthrough, implemented with function and flag

#! /bin/sh

switch_fallthrough() {
  [ $# = 0 ] && { echo no value >&2; return; }
  local f= # fall through
  [ "$1" = 1 ] && { echo quick; f=1; }
  [ $f ] || [ "$1" = 2 ] && { echo brown; f=1; }
  [ $f ] || [ "$1" = 3 ] && { echo fox; return; }
  [ $f ] || [ "$1" = 4 ] && echo jumped
  return 1 # error = no case did match
}

switch_fallthrough "2"
# brown
# fox
Mila Nautikus
  • 1,481
  • 1
  • 9
  • 18