Existe uma declaração “goto” no bash?

Existe uma declaração “goto” no bash? Eu sei que é considerado uma prática ruim, mas eu preciso especificamente “goto”.

   

Não, não há; veja §3.2.4 “Comandos Compostos” no Manual de Referência do Bash para obter informações sobre as estruturas de controle que existem. Em particular, observe a menção de break and continue , que não é tão flexível quanto o goto , mas é mais flexível no Bash do que em alguns idiomas e pode ajudá-lo a alcançar o que você deseja. (O que quer que você queira …)

Se você estiver usando para pular parte de um script grande para debugging (veja o comentário de Karl Nicoll), então se false pode ser uma boa opção (não tenho certeza se “false” está sempre disponível, para mim está em / bin / false) :

 # ... Code I want to run here ... if false; then # ... Code I want to skip here ... fi # ... I want to resume here ... 

A dificuldade surge quando é hora de extrair seu código de debugging. O constructo “se falso” é bastante direto e memorável, mas como você encontra o fi? Correspondente? Se o seu editor permitir que você bloqueie o recuo, você poderá recuar o bloco ignorado (então você vai querer colocá-lo de volta quando terminar). Ou um comentário sobre a linha, mas teria de ser algo que você lembraria, que eu suspeito que seja muito dependente do programador.

Na verdade, pode ser útil para algumas necessidades de debugging ou demonstração.

Eu encontrei essa solução de Bob Copeland http://bobcopeland.com/blog/2012/10/goto-in-bash/ elegant:

 #!/bin/bash # include this boilerplate function jumpto { label=$1 cmd=$(sed -n "/$label:/{:a;n;p;ba};" $0 | grep -v ':$') eval "$cmd" exit } start=${1:-"start"} jumpto $start start: # your script goes here... x=100 jumpto foo mid: x=101 echo "This is not printed!" foo: x=${x:-10} echo x is $x 

resulta em:

 $ ./test.sh x is 100 $ ./test.sh foo x is 10 $ ./test.sh mid This is not printed! x is 101 

Você pode usar case no bash para simular um goto:

 #!/bin/bash case bar in foo) echo foo ;& bar) echo bar ;& *) echo star ;; esac 

produz:

 bar star 

Embora outros já tenham esclarecido que não há goto equivalente direto no bash (e desde que as alternativas mais próximas, como funções, loops e break), eu gostaria de ilustrar como usar um loop mais break pode simular um tipo específico de instrução goto.

A situação em que acho isso mais útil é quando preciso retornar ao início de uma seção de código se determinadas condições não forem atendidas. No exemplo abaixo, o loop while será executado para sempre até que o ping pare de descartar pacotes para um IP de teste.

 #!/bin/bash TestIP="8.8.8.8" # Loop forever (until break is issued) while true; do # Do a simple test for Internet connectivity PacketLoss=$(ping "$TestIP" -c 2 | grep -Eo "[0-9]+% packet loss" | grep -Eo "^[0-9]") # Exit the loop if ping is no longer dropping packets if [ "$PacketLoss" == 0 ]; then echo "Connection restored" break else echo "No connectivity" fi done 

Se você está testando / depurando um script bash, e simplesmente quer pular para frente depois de uma ou mais seções de código, aqui está uma maneira muito simples de fazer isso que também é muito fácil de encontrar e remover depois (diferente da maioria dos methods) descrito acima).

 #!/bin/bash echo "Run this" cat >/dev/null < /dev/null <  

Para colocar seu script de volta ao normal, basta excluir as linhas com GOTO .

Nós também podemos embelezar esta solução, adicionando um comando goto como um alias:

 #!/bin/bash shopt -s expand_aliases alias goto="cat >/dev/null < <" goto GOTO_1 echo "Don't run this" GOTO_1 echo "Run this" goto GOTO_2 echo "Don't run this either" GOTO_2 echo "All done" 

Os aliases geralmente não funcionam em scripts bash, então precisamos do comando shopt para consertar isso.

Se você quer poder ativar / desativar seu goto , precisamos de um pouco mais:

 #!/bin/bash shopt -s expand_aliases if [ -n "$DEBUG" ] ; then alias goto="cat >/dev/null < <" else alias goto=":" fi goto '#GOTO_1' echo "Don't run this" #GOTO1 echo "Run this" goto '#GOTO_2' echo "Don't run this either" #GOTO_2 echo "All done" 

Então você pode export DEBUG=TRUE antes de executar o script.

Os labels são comentários, portanto, não causará erros de syntax se desativarmos o nosso goto (configurando goto para ' : ' no-op), mas isso significa que precisamos citá-los em nossas instruções goto .

Sempre que estiver usando qualquer tipo de solução goto , você precisa ter cuidado para que o código que você está pulando não defina as variables ​​nas quais você confia posteriormente - você pode precisar mover essas definições para o topo do seu script, ou apenas acima uma de suas declarações goto .

Há mais uma habilidade para alcançar os resultados desejados: trap comando. Pode ser usado para fins de limpeza, por exemplo.

Não há goto no bash.

Aqui está alguma solução suja usando trap que salta apenas para trás 🙂

 #!/bin/bash -e trap ' echo I am sleep 1 echo here now. ' EXIT echo foo goto trap 2> /dev/null echo bar 

Saída:

 $ ./test.sh foo I am here now. 

Isso não deve ser usado dessa maneira, mas apenas para fins educacionais. Aqui está o porquê isso funciona:

trap está usando exception handling para conseguir a mudança no stream de código. Nesse caso, a trap está capturando qualquer coisa que faça com que o script seja EXIT. O comando goto não existe e, portanto, gera um erro, que normalmente sairia do script. Este erro está sendo capturado com o trap e o 2>/dev/null oculta a mensagem de erro que normalmente seria exibida.

Esta implementação do goto obviamente não é confiável, já que qualquer comando inexistente (ou qualquer outro erro, dessa forma), executaria o mesmo comando trap. Em particular, você não pode escolher qual label ir para.


Basicamente, no cenário real, você não precisa de nenhuma instrução goto, elas são redundantes, pois chamadas aleatórias para diferentes locais tornam seu código difícil de entender.

Se seu código for invocado muitas vezes, considere usar o loop e alterar seu stream de trabalho para usar continue e break .

Se o seu código se repetir, considere escrever a function e chamá-la quantas vezes quiser.

Se seu código precisar pular para uma seção específica com base no valor da variável, considere o uso da instrução case .

Se você puder separar seu código longo em partes menores, considere transferi-lo para arquivos separados e chamá-los do script pai.

Eu descobri uma maneira de fazer isso usando funções.

Digamos, por exemplo, você tem 3 opções: A , B e C A e B executam um comando, mas C lhe dá mais informações e o leva ao prompt original novamente. Isso pode ser feito usando funções.

Observe que, como a function demoFunction da linha contendo a function demoFunction está apenas configurando a function, é necessário chamar demoFunction após esse script para que a function seja realmente executada.

Você pode facilmente adaptar isso escrevendo várias outras funções e chamando-as se precisar ” GOTO ” em outro lugar em seu script de shell.

 function demoFunction { read -n1 -p "Pick a letter to run a command [A, B, or C for more info] " runCommand case $runCommand in a|A) printf "\n\tpwd being executed...\n" && pwd;; b|B) printf "\n\tls being executed...\n" && ls;; c|C) printf "\n\toption A runs pwd, option B runs ls\n" && demoFunction;; esac } demoFunction 

Esta é uma pequena correção do roteiro de Judy Schmidt, apresentado por Hubbbitus.

Colocar labels não escapados no script foi problemático na máquina e causou a falha. Isso foi fácil o suficiente para resolver adicionando # para escaping dos labels. Graças a Alexej Magura e access_granted por suas sugestões.

 #!/bin/bash # include this boilerplate function goto { label=$1 cmd=$(sed -n "/$#label#:/{:a;n;p;ba};" $0 | grep -v ':$') eval "$cmd" exit } start=${1:-"start"} goto $start #start# echo "start" goto bing #boom# echo boom goto eof #bang# echo bang goto boom #bing# echo bing goto bang #eof# echo "the end mother-hugger..."