10

Without ;;&, /bin/sh gives Syntax error: ")" unexpected (expecting ";;").

;;& triggers shellcheck's ^-- SC2127 (error): To use cases with ;;&, specify #!/usr/bin/env bash notice.

Other than not using /bin/sh, how to fallthrough? Have no wish to use /bin/sh, but /bin/sh is the "lowest common denominator" (of which all Unix shells are supersets), so cross-platform code is supposed to use this.

The specific use for this particular fall-through is this function, which has this switch:

 case "${5}" in #/* Language determines which flags to use */ "C") export CFLAGS="${CFLAGS} ${1}${SUSUWU_DEPENDENCY_INCLUDE_PATH}" export CXXFLAGS="${CXXFLAGS} ${1}${SUSUWU_DEPENDENCY_INCLUDE_PATH}" #/* As far as lib support, C++ is a superset */ ;; "C++") export CXXFLAGS="${CXXFLAGS} ${1}${SUSUWU_DEPENDENCY_INCLUDE_PATH}";; *) #/* Include flags for unknown languages export to all build scripts */ export FLAGS_USER="${FLAGS_USER} ${1}${SUSUWU_DEPENDENCY_INCLUDE_PATH}";; #/* Todo: allow to use this after `SUSUWU_SETUP_BUILD_FLAGS()` */ esac 

The wish is to remove the duplicate export CXXFLAGS="${CXXFLAGS} ${1}${SUSUWU_DEPENDENCY_INCLUDE_PATH}" if the "C" code flow can fall through to the "C++" code flow.

The reason to remove duplicate code is to reduce the risk that future versions (with more code) will end up with 1 flow updated out-of-sync with the other flow.

6
  • Note that /bin/sh doesn't identify a particular shell.  On some systems it points to bash (in a POSIX-compliant mode); on others it may be dash or even the original Bourne shell.  All you know is that it's POSIX-compliant; you can't tell what extra features it may support.  (Even in POSIX mode, bash supports lots of things that other shells don't.  — Which means that running on /bin/sh doesn't prove that your script is POSIX-compliant…  Dash is much more suited to that.) Commented Sep 11 at 23:23
  • Can you give an example where you need a fall-through? Commented Sep 12 at 7:22
  • Thank you for your question, I use bash since ... many decades and never saw the "&;" fallthrough facility in case Commented Sep 12 at 11:17
  • @U.Windl: Improved question to include the fallthrough's use. Dulac: am glad others found use for this. Commented Sep 12 at 18:29
  • The fact that you want this means your script is reaching the limit of what is reasonable to do in shell. If you have the option of using any shell extensions whatsoever, then you also have the option of writing your script in a language which isn't as limited as shell. You should consider that option before you go looking for anything that isn't obviously possible in portable shell. (I wrote more exposition on this point in these answers: unix.stackexchange.com/a/250935 and unix.stackexchange.com/a/129120 .) Commented Sep 13 at 14:12

4 Answers 4

12

Note that bash's ;;& (added in 4.0 in 2009) is the equivalent of zsh's ;| (from 2007; also in mksh since 2011), it's not a fall through in the sense of C's fallthrough when you omit the break in a switch statement in that the next pattern will still be checked.

As in bash's

case a in (a) echo a;;& (b) echo b esac 

Or zsh's / mksh's

case a in (a) echo a;| (b) echo b esac 

Won't output both a and b like a

switch('a') { case 'a': puts("a"); /* fall through */ case 'b': puts("b"); } 

would in C.

For that, you'd need ;& instead (itself from ksh93 and found in all of bash, zsh and mksh; while ;; and the whole case...esac structure is from the Bourne shell from the late 70s).

Also, in bash's

case $string in (a*) echo starts with a;;& (*b) echo ends with b;; (*c*) echo neither starts with a nor ends in b but contains c esac 

That is if you mix ;;& and ;;, then you can't just do pattern matching in sequence independently as a standard equivalent.

In that example, the *c* case will only be reached if the string matches neither a* not *b.

One approach can be to do all 3 checks and then consider all combinations:

test1=0 test2=0 test3=0 case $string in (a*) test1=1 esac case $string in (*b) test2=1 esac case $string in (*c*) test3=1 esac case $test1$test2$test3 in (11?) echo starts with a echo ends in b;; (10?) echo start with a;; (01?) echo ends in b;; (??1) echo neither starts with a nor ends in b but contains c esac 

Or adapt on a case by case basis, using a combination of case and if and/or &&/||, like here:

flag=false case $string in (a*) echo starts with a flag=true esac case $string in (*b) echo ends in b flag=true esac "$flag" || case $string in (*c*) echo neither starts with a nor ends in b but contains c esac 

Or do it all with if/then/else, using flags or repeating the checks like in:

match() case $1 in ($2) true;; (*) false; esac if match "$string" 'a*'; then echo starts with a fi if match "$string" '*b'; then echo ends in a fi if ! match "$string" 'a*' && ! match "$string" '*b' && match "$string" '*c*' then echo neither starts with a nor ends in b but contains c fi 

Which may make it easier to follow.

With the use-case you've now added:

case $5 in (C | C++) export CXXFLAGS="${CXXFLAGS} ${1}${SUSUWU_DEPENDENCY_INCLUDE_PATH}" esac case $5 in (C) export CFLAGS="${CFLAGS} ${1}${SUSUWU_DEPENDENCY_INCLUDE_PATH}";; (C++) ;; (*) export FLAGS_USER="${FLAGS_USER} ${1}${SUSUWU_DEPENDENCY_INCLUDE_PATH}" esac 

Or:

c_or_cxx=false case $5 in (C) export CFLAGS="${CFLAGS} ${1}${SUSUWU_DEPENDENCY_INCLUDE_PATH}" c_or_cxx=true;; (C++) c_or_cxx=true;; (*) export FLAGS_USER="${FLAGS_USER} ${1}${SUSUWU_DEPENDENCY_INCLUDE_PATH}" esac if "$c_or_cxx"; then export CXXFLAGS="${CXXFLAGS} ${1}${SUSUWU_DEPENDENCY_INCLUDE_PATH}" fi 

You can also include the common code in a function or variable to eval (variable may be preferable here as the code includes references to $1):

for_c_and_cxx=' export CXXFLAGS="${CXXFLAGS} ${1}${SUSUWU_DEPENDENCY_INCLUDE_PATH}" ' case $5 in (C) export CFLAGS="${CFLAGS} ${1}${SUSUWU_DEPENDENCY_INCLUDE_PATH}" eval "$for_c_and_cxx";; (C++) eval "$for_c_and_cxx";; (*) export FLAGS_USER="${FLAGS_USER} {1}${SUSUWU_DEPENDENCY_INCLUDE_PATH}" esac 

Note that it's only the Bourne shell that required you omitted the opening (s (the Bourne shell also did not support export var=value and in any case was not a POSIX compliant shell). In modern/standard sh implementations, you can include it which IMO makes for more readable code and lets you use your editors ability to check for matching parenthesis for instance.

1
  • Cool. Sounds as if ;& (which is not available in /bin/sh) is the most close to C++ fallthroughs, but that alternative codeflows can simulate C++ fallthroughs. Commented Sep 11 at 22:47
7

bash

var='fooo' case "$var" in foo*) echo 2 ;;& fooo*) echo 3 ;;& esac 

sh

case "$var" in foo*) echo 2 ;; esac case "$var" in fooo*) echo 3 ;; esac 
1

FWIW I'd keep it simple and use the case statement to decide what needs to happen and then a series of ifs to implement that desired behavior:

case "${5}" in #/* Language determines which flags to use */ "C") set_cflags=1; set_cxxflags=1 ;; "C++") set_cxxflags=1 ;; *) set_flags_user=1 ;; esac if [ "$set_cflags" = 1 ]; then export CFLAGS="${CFLAGS} ${1}${SUSUWU_DEPENDENCY_INCLUDE_PATH}" fi if [ "$set_cxxflags" = 1 ]; then export CXXFLAGS="${CXXFLAGS} ${1}${SUSUWU_DEPENDENCY_INCLUDE_PATH}" fi if [ "$set_flags_user" = 1 ]; then export FLAGS_USER="${FLAGS_USER} ${1}${SUSUWU_DEPENDENCY_INCLUDE_PATH}" fi 

I just read the POSIX spec and see POSIX shell supports functions so you could alternatively do:

set_cflags() { export CFLAGS="${CFLAGS} ${1}${SUSUWU_DEPENDENCY_INCLUDE_PATH}"; } set_cxxflags() { export CXXFLAGS="${CXXFLAGS} ${1}${SUSUWU_DEPENDENCY_INCLUDE_PATH}"; } set_flagsuser() { export FLAGS_USER="${FLAGS_USER} ${1}${SUSUWU_DEPENDENCY_INCLUDE_PATH}"; } case "${5}" in #/* Language determines which flags to use */ "C") set_cflags "$1"; set_cxxflags "$1";; "C++") set_cxxflags "$1";; *) set_flagsuser "$1";; esac 
5
  • 1
    Perhaps not to your taste, but an option I sometimes use is for the variables to hold commands - namely false or true. Then we are able to if $set_cflags with no need to execute [. Commented Oct 29 at 12:50
  • @TobySpeight it's been so long since I used sh (40 years maybe?), that a) I don't remember much about it's syntax and, b) I think it's syntax may have evolved since I last used it, and I don't have sh on any machine I have access to to test with so I was just sticking to the basics I was 99% sure of :-). If storing a true/false command in the variable works - sounds good. Although, I expect that'd mean you'd need to add code to initialise them all to false before the case but that's not a bad idea anyway. Commented Oct 29 at 12:53
  • If you have a system that doesn't have a sh utility, then it's off-topic here. Don't confuse sh (noways an interpreter for the POSIX shell language based on a subset of the ksh88 language) with the Bourne shell (its predecessor) or its clones or the Thompson shell which predates it. There are POSIX sh implementations based on the Bourne shell such as bosh or ksh88 and some based on some of its clones such as dash (based on Almquist shell) or mksh (based on Forsyth shell) Commented Oct 29 at 13:44
  • 1
    @Ed, yes I did mean that the variables should be set false before the code in question. There are alternatives (e.g. evaluating with a fallback: ${set_cflags-false}), but I feel that then goes backwards again on the readability. Commented Oct 29 at 14:15
  • @StéphaneChazelas when you said noways - was that a typo and you meant nowadays? I suspect I have been thinking of the Bourne shell from days of yore when I think of sh. Commented Oct 29 at 14:44
0

In most languages a "case" or "switch" statement does not have a "fall-through". In C it's a low-level performance optimization for re-using common code, but you frequently get compiler warnings when using it. So if you have a lot of common code, consider to put that in a function, or replace the case with some if... [elif ...]... fi structure.

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.