13

I am looking for a way to have fallthrough happen based on an if condition within a case condition in bash. For example:

input="foo"
VAR="1"

case $input in
foo)
    if [ $VAR = "1" ]; then

        # perform fallthrough

    else

        # do not perform fallthrough

    fi
;;
*)
    echo "fallthrough worked!"
;;
esac

In the above code, if the variable VAR is 1, I would like to have the case condition perform fallthrough.

1
  • 1
    Small question: Are you trying to jump from if [ $VAR -eq 1 ]; then part of the code to whatever is in *) ? Because that's entirely different from what fallthrough is called, thus making your question phrasing just slightly misleading. May 28, 2018 at 20:24

8 Answers 8

9

You can't. The way to have a case fall through is to replace the ;; separator with ;& (or ;;&). And it's a syntax error to put that inside an if.

You could write the whole logic out as a regular conditional:

if [ "$input" != "foo" ] || [ "$VAR" = 1 ]; then
    one branch ...
else   # $input = "foo" && $VAR != 1
    another branch...
fi
3
  • Yes, He can ! :)
    – user232326
    May 29, 2018 at 8:46
  • @Isaac, well, yeah, though they did specify basing the fallthrough on an if.
    – ilkkachu
    May 29, 2018 at 9:10
  • +1 To be clear: I up voted your answer. :).
    – user232326
    May 29, 2018 at 9:16
8

The following script turns your test "inside out" in the sense that we test $var first and then perform the fallthrough (using ;& in a case) depending on $input.

We do this because the question of whether or not to "perform the fallthrough" is really only dependent on $input if $var is 1. If it's any other value, the question of whether to do the fallthrough does not even have to be asked.

#/bin/bash

input='foo'
var='1'

case $var in
    1)
        case $input in
            foo)
                echo 'perform fallthrough'
                ;&
            *)
                echo 'fallthough worked'
        esac
        ;;
    *)
        echo 'what fallthrough?'
esac

Or, without case:

if [ "$var" -eq 1 ]; then
    if [ "$input" = 'foo' ]; then
        echo 'perform fallthrough'
    fi
    echo 'fallthough worked'
else
    echo 'what fallthrough?'
fi
1
  • I think you nailed what OP actually wanted, which seems to be jumping from their original if statement to whatever in *). Already have my +1. May 28, 2018 at 20:26
7

I'd suggest restructuring your logic: put the "fallthrough" code into a function instead:

fallthrough() { echo 'fallthrough worked!'; }

for input in foo bar; do
    for var in 1 2; do
        echo "$input $var"
        case $input in
            foo)
                if (( var == 1 )); then
                    echo "falling through"
                    fallthrough
                else
                    echo "not falling through"
                fi
            ;;
            *) fallthrough;;
        esac
    done
done

outputs

foo 1
falling through
fallthrough worked!
foo 2
not falling through
bar 1
fallthrough worked!
bar 2
fallthrough worked!
5

Not something that I would do, but you could achieve something approaching with:

shopt -s extglob # for !(*)
default='*'
case $input in
  (foo)
    if [ "$VAR" = 1 ]; then
      echo going for fallthrough
    else
      echo disabling fallthrough
      default='!(*)'
    fi ;;&

  ($default)
    echo fallthrough
esac
3

Test both variables at once (bash 4.0-alpha+):

#!/bin/bash
while (($#>1)); do
    input=$1    VAR=$2
    echo "input=${input} VAR=${VAR}"; shift 2

    if [ "$VAR" = 1 ]; then new=1; else new=0; fi

    case $input$new in
    foo0)   echo "do not perform fallthrough"   ;;
    foo*)   echo "perform fallthrough"          ;&
    *)      echo "fallthrough worked!"          ;;
    esac

    echo
done

On testing:

$ ./script foo 0   foo 1   bar baz
input=foo VAR=0
do not perform fallthrough

input=foo VAR=1
perform fallthrough
fallthrough worked!

input=bar VAR=baz
fallthrough worked!

Clean and simple.

Understand that the tested value ($new) must have only two possible values, that is why the if clause is there, to transform VAR to a Boolean value. If VAR may be made to be a Boolean, then test for 0 (not 1) in the case and remove the if.

1

You can make the fallthrough default but place a condition that the code only executes only if the condition is met

#!/bin/bash

input='foo'
var='1'

case $input in
foo)
        echo "Do fall through"
;& #always fall through
*)
        if [ $var = "1" ] #execute only if condition matches
        then
        echo "fallthrough took place"
        fi
esac

But as ilkkachu suggested you can also use conditions rather than switch.

1

If you don't mind someone complaining about they don't understand your code, you could simply switch the order of the two conditionals:

input="foo"
VAR="1"

if 
    case $input in
    foo)
        [ $VAR = "1" ]
    ;;
    esac
then
    echo "fallthrough worked!"
fi

Or:

input="foo"
VAR="1"

case $input in
foo)
    [ $VAR = "1" ]
;;
esac &&
    echo "fallthrough worked!"

Simple and clear (at least to me). case doesn't support fallthrough itself. But you can replace *) with && after esac to make it respect to the return values of other branches.

-3

New ;& and ;;& operators were introduced in Bash 4.0 and although they both might be useful in similar situations I think they are of no use in your case. This is what man bash says about these operators:

If the ;; operator is used, no subsequent matches are attempted after the first pattern match. Using ;& in place of ;; causes execution to continue with the list associated with the next set of patterns. 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.

In other words, ;& is a fall through and as we know it from C and ;;& makes bash check remaining cases instead of returning from case block entirely. You can find a nice example of ;;& in action here: https://stackoverflow.com/a/24544780/3691891.

That being said, neither ;& nor ;;& could be used in your script because both of them would go to *) that would be always run.

The following script works and does what you want without re-arranging the logic but consider it only as an example and never rely on it, it's too fragile. I've taken the idea from here:

#!/usr/bin/env bash

function jumpto
{
    label=$1
    cmd=$(sed -n "/$label:/{:a;n;p;ba};" "$0" | grep -v ':$')
    cmd=$(echo "$cmd" | sed 's,;;,,')
    cmd=$(echo "$cmd" | sed 's,esac,,')
    eval "$cmd"
}

input="foo"
VAR="1"

case $input in
foo)
    if [ $VAR = "1" ]; then

        printf "perform fallthrough\n"
        jumpto ft
    else
        printf "do not perform fallthrough\n"

    fi
;;
*)
    ft:
    echo "fallthrough worked!"
;;
esac
3
  • Why a downvote? May 28, 2018 at 20:15
  • 1
    This may work in a toy example but it's near-incomprehensible and wouldn't work in a larger script. The way you simulate goto is extremely fragile, and to say that it really simulates goto is an exaggeration. The code returns from the jumpto function and executes whatever comes after it. The fact that this is equivalent to continuing executing after the block between ft: and ;; is a coincidence because jumpto is the last command in the same complex command as the jumped-to block. May 28, 2018 at 20:59
  • 1
    OK, I clearly emphasized that in my answer. May 28, 2018 at 21:01

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .