Revisão do caso com base na condição if

11

Eu estou procurando uma maneira de obter resultados com base em uma condição if dentro de uma condição de caso no bash. Por exemplo:

input="foo"
VAR="1"

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

        # perform fallthrough

    else

        # do not perform fallthrough

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

No código acima, se a variável VARfor 1, eu gostaria que a condição do caso fosse executada.

Smashgen
fonte
1
Pequena pergunta: você está tentando pular de if [ $VAR -eq 1 ]; thenparte do código para o que quer que esteja *)? Porque isso é totalmente diferente do que é chamado de avanço, tornando a sua pergunta um pouco enganosa.
Sergiy Kolodyazhnyy

Respostas:

8

Você não pode. A maneira de obter uma casequeda é substituir o ;;separador por ;&(ou ;;&). E é um erro de sintaxe colocar isso dentro de um if.

Você pode escrever toda a lógica como uma condicional regular:

if [ "$input" != "foo" ] || [ "$VAR" = 1 ]; then
    one branch ...
else   # $input = "foo" && $VAR != 1
    another branch...
fi
ilkkachu
fonte
Sim ele pode ! :)
NotAnUnixNazi
@ Isaac, bem, sim, embora eles especificassem basear a descoberta em um if.
Ilkkachu 29/05
+1 Para ser claro: votei na sua resposta. :).
NotAnUnixNazi
7

Eu sugiro reestruturar sua lógica: coloque o código "fallthrough" em uma função:

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

saídas

foo 1
falling through
fallthrough worked!
foo 2
not falling through
bar 1
fallthrough worked!
bar 2
fallthrough worked!
Glenn Jackman
fonte
7

O script a seguir transforma seu teste "de dentro para fora", no sentido em que testamos $varprimeiro e, em seguida, realizamos o procedimento (usando ;&em a case), dependendo $input.

Fazemos isso porque a questão de "executar a descoberta" deve ou não ser realmente apenas dependente de $inputse $varé 1. Se houver algum outro valor, a questão de fazer a descoberta nem precisa ser feita.

#/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

Ou, sem case:

if [ "$var" -eq 1 ]; then
    if [ "$input" = 'foo' ]; then
        echo 'perform fallthrough'
    fi
    echo 'fallthough worked'
else
    echo 'what fallthrough?'
fi
Kusalananda
fonte
Acho que você acertou o que o OP realmente queria, o que parece estar saltando da declaração if original para o que quer que seja *). Já tenho meu +1.
Sergiy Kolodyazhnyy
5

Não é algo que eu faria, mas você poderia conseguir algo com:

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
Stéphane Chazelas
fonte
2

Teste as duas variáveis ​​de uma vez (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

No teste:

$ ./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!

Limpo e simples.

Entenda que o valor testado ( $new) deve ter apenas dois valores possíveis, é por isso que a cláusula if existe, para transformar VAR em um valor booleano. Se o VAR puder ser um booleano, teste 0(não 1) no casee remova o if.

NotAnUnixNazi
fonte
1

Você pode tornar a substituição padrão, mas colocar uma condição que o código executa apenas se a condição for atendida

#!/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

Mas como o ilkkachu sugeriu, você também pode usar condições em vez de mudar.

yashC
fonte
1

Se você não se importa com alguém reclamando, ele não entende seu código, basta mudar a ordem dos dois condicionais:

input="foo"
VAR="1"

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

Ou:

input="foo"
VAR="1"

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

Simples e claro (pelo menos para mim). casenão suporta queda por si só. Mas você pode substituir *)por &&after esacpara fazer respeitar os valores de retorno de outras ramificações.

user23013
fonte
-4

Os operadores new ;&e ;;&foram introduzidos na Bash 4.0 e, embora ambos possam ser úteis em situações semelhantes, acho que não são úteis no seu caso. É o que man bashdiz sobre esses operadores:

Se o ;; Se o operador for usado, nenhuma correspondência subsequente será tentada após a primeira correspondência de padrão. Usando; & no lugar de ;; faz com que a execução continue com a lista associada ao próximo conjunto de padrões. Usando ;; & no lugar de ;; faz com que o shell teste a próxima lista de padrões na instrução, se houver, e execute qualquer lista associada em uma correspondência bem-sucedida.

Em outras palavras, ;&é uma queda através de e como a conhecemos a partir Ce ;;&marcas bashverificar restantes casos, em vez de retornar de casebloco inteiramente. Você pode encontrar um bom exemplo de ;;&ação aqui: /programming//a/24544780/3691891 .

Dito isto, nem ;&nem ;;&poderia ser usado em seu script, porque os dois iriam para o *)que sempre seriam executados.

O script a seguir funciona e faz o que você deseja sem reorganizar a lógica, mas considere-a apenas como um exemplo e nunca confie nela, é muito frágil. Eu peguei a ideia daqui :

#!/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
Arkadiusz Drabczyk
fonte
Por que um voto negativo?
Arkadiusz Drabczyk 28/05
1
Isso pode funcionar em um exemplo de brinquedo, mas é quase incompreensível e não funcionaria em um script maior. A maneira como você simula goto é extremamente frágil, e dizer que realmente simula goto é um exagero. O código retorna da jumptofunção e executa o que vier depois dela. O fato de isso ser equivalente a continuar executando após o bloco entre ft:e ;;é uma coincidência, porque jumptoé o último comando no mesmo comando complexo que o bloco saltado para.
Gilles 'SO- stop be evil'
1
OK, enfatizei claramente isso na minha resposta.
Arkadiusz Drabczyk