Existe uma declaração "goto" no bash?

206

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

kofucii
fonte
4
Não, não há gotono bash (pelo menos diz command not foundpara mim). Por quê? Provavelmente, existe uma maneira melhor de fazê-lo.
Niklas B.
153
Ele pode ter suas razões. Encontrei essa pergunta porque quero que uma gotoinstrução pule muito código para depurar um script grande sem esperar uma hora para que várias tarefas não relacionadas sejam concluídas. Eu certamente não usaria um gotono código de produção, mas, para depurar meu código, tornaria minha vida infinitamente mais fácil, e seria mais fácil identificar quando se tratava de removê-lo.
10136 Karl-Nicoll
30
@ delnan Mas não ter que ir pode tornar algumas coisas mais complicadas. De fato, existem casos de uso.
glglgl
71
Estou farto desse mito goto! Não há nada errado em ir! Tudo o que você escreve acaba se tornando inútil. No assembler, existe apenas goto. Uma boa razão para usar goto em linguagens de programação mais altas é, por exemplo, saltar de loops aninhados de maneira limpa e legível.
Alexander Czutro
25
"Evite ir" é uma ótima regra. Como qualquer regra, deve ser aprendida em três fases. Primeiro: siga a regra até sua segunda natureza. Segundo, aprenda a entender os motivos da regra. Terceiro, aprenda boas exceções à regra, com base em um entendimento abrangente de como seguir a regra e os motivos da regra. Evite pular as etapas acima, o que seria como usar "goto". ;-)
Jeff Learman

Respostas:

75

Não, não há; ver §3.2.4 "Comandos compostos" na Bash Manual de Referência para obter informações sobre as estruturas de controle que fazer existir. Em particular, observe a menção de breake continue, que não é tão flexível quanto goto, mas é mais flexível no Bash do que em alguns idiomas, e pode ajudá-lo a alcançar o que deseja. (O que você quiser.)

ruakh
fonte
10
Você poderia expandir para "mais flexível no Bash do que em alguns idiomas"?
user239558
21
@ user239558: Alguns idiomas permitem apenas o loop interno breakou a continuepartir do loop interno, enquanto o Bash permite especificar quantos níveis de loop serão saltos. (E mesmo em idiomas que permitem breakou continuede loops arbitrários, a maioria exige que seja expresso estaticamente - por exemplo, break foo;sairá do loop rotulado foo- enquanto no Bash é expresso dinamicamente - por exemplo, break "$foo"sairá de $fooloops. )
ruach
Eu recomendo definir funções com os recursos necessários e chamá-las de outras partes do código.
blmayer
@ruakh Na verdade, muitos idiomas permitem break LABEL_NAME;, em vez do nojento de Bash break INTEGER;.
Sapphire_Brick
1
@Sapphire_Brick: Sim, eu mencionei isso no comentário que você está respondendo.
ruakh 29/06
124

Se você o estiver usando para pular parte de um script grande para depuração (consulte o comentário de Karl Nicoll), se false puder 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 depuração. A construção "se falsa" é bem direta e memorável, mas como você encontra o fi correspondente? Se o seu editor permitir bloquear o recuo, você poderá recuar o bloco ignorado (será necessário recuperá-lo quando terminar). Ou um comentário sobre a linha fi, mas teria que ser algo que você se lembraria, que eu suspeito que seja muito dependente do programador.

Michael Rusch
fonte
6
Sim falseestá sempre disponível. Mas se você tem um bloco de código que não deseja executar, basta comentar. Ou exclua-o (e procure no seu sistema de controle de origem se precisar recuperá-lo mais tarde).
perfil completo de Keith Thompson
1
Se o bloco de código for muito longo para comentar tediosamente uma linha de cada vez, veja esses truques. stackoverflow.com/questions/947897/… No entanto, isso também não ajuda um editor de texto a corresponder do início ao fim, portanto, não é uma grande melhoria.
Camille Goudeseune
4
"se falso" geralmente é muito melhor do que comentar o código, porque garante que o código incluído continue sendo o código legal. A melhor desculpa para comentar o código é quando ele realmente precisa ser excluído, mas há algo que precisa ser lembrado - portanto, é apenas um comentário e não mais "código".
Jeff Learman
1
Eu uso o comentário '#if false;'. Dessa forma, eu posso apenas procurar por isso e encontrar o início e o final da seção de remoção de depuração.
Jon
Esta resposta não deve ser votada, pois não responde à pergunta do OP de forma alguma.
21719 Viktor Joras
54

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

Descobri que a solução Bob Copeland http://bobcopeland.com/blog/2012/10/goto-in-bash/ elegante:

#!/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
Hubbitus
fonte
36
"Minha busca para fazer o bash parecer linguagem assembly se aproxima cada vez mais da conclusão." - Uau. Apenas Uau.
Brian Agnew
5
a única coisa que eu mudaria é fazer com que os rótulos comecem assim, : start:para que não sejam erros de sintaxe.
Alexej Magura
5
Seria melhor se você fizesse isso: cmd = $ (sed -n "/ # $ label: / {: a; n; p; ba};" $ 0 | grep -v ': $') com rótulos iniciados como: # start: => isso evitaria erros de script
access_granted
2
Hm, na verdade, provavelmente é o meu erro. Eu tenho editar postagem. Obrigado.
achou do artigo
2
set -xajuda a entender o que está acontecendo
John Lin
30

Você pode usar caseno bash para simular um goto:

#!/bin/bash

case bar in
  foo)
    echo foo
    ;&

  bar)
    echo bar
    ;&

  *)
    echo star
    ;;
esac

produz:

bar
star
Paul Brannan
fonte
4
Observe que isso requer bash v4.0+. No entanto, não é uma opção de uso geral, gotomas uma opção indireta para a casedeclaração.
usar o seguinte código
3
Eu acho que essa deve ser a resposta. Eu tenho uma necessidade genuína de ir para, a fim de oferecer suporte à retomada da execução de um script, a partir de uma determinada instrução. isto é, em todos os aspectos, exceto açúcares semânticos, goto e semânticos e sintáticos são engraçados, mas não estritamente necessários. ótima solução, IMO.
Nathan g 23/03
1
@nathang, se a resposta depende de seu caso coincidir com o subconjunto do caso geral sobre o qual o OP perguntou. Infelizmente, a pergunta é feita sobre o caso geral, tornando essa resposta muito estreita para ser correta. (Se essa pergunta deve ser encerrada como muito ampla por esse motivo é uma discussão diferente).
Charles Duffy
1
Ir é mais do que selecionar. meios de recurso Goto para ser capaz de saltar para lugares monitores segundo a algumas condições, criando ainda um loop como fluxo ...
Sergio Abreu
19

Se você estiver testando / depurando um script bash e simplesmente quiser pular adiante 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 mais tarde (diferente da maioria dos métodos descrito acima).

#!/bin/bash

echo "Run this"

cat >/dev/null <<GOTO_1

echo "Don't run this"

GOTO_1

echo "Also run this"

cat >/dev/null <<GOTO_2

echo "Don't run this either"

GOTO_2

echo "Yet more code I want to run"

Para colocar seu script de volta ao normal, exclua todas as linhas com GOTO.

Também podemos prettificar esta solução, adicionando um gotocomando 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, por isso precisamos do shoptcomando para corrigir isso.

Se você deseja ativar / desativar os seus 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 fazer export DEBUG=TRUEantes de executar o script.

Os rótulos são comentários, portanto, não causam erros de sintaxe se desativarmos os nossos goto(definindo gotocomo ' :' não-op ' '), mas isso significa que precisamos citá-los em nossogoto declarações.

Sempre que usar qualquer tipo de gotosolução, você deve tomar cuidado para que o código que você está passando não defina nenhuma variável em que você confia mais tarde - talvez seja necessário mover essas definições para a parte superior do seu script, ou logo acima de uma de suas gotodeclarações.

Laurence Renshaw
fonte
Sem entrar no bom / mau do goto, a solução de Laurence é simplesmente legal. Você conseguiu meu voto.
Mogens TrasherDK
1
É mais como um pular para, e if(false)parece fazer mais sentido (para mim).
Vesperto
2
Citar o rótulo "goto" também significa que o documento aqui não é expandido / avaliado, o que evita alguns erros difíceis de encontrar (sem aspas, ele estaria sujeito a: expansão de parâmetro, substituição de comando e expansão aritmética, que todos podem ter efeitos colaterais). Você também pode ajustar isso um pouco alias goto=":<<"e dispensar catcompletamente.
precisa saber é o seguinte
sim, vesperto, você pode usar uma declaração 'if', e eu fiz isso em muitos scripts, mas fica muito confuso e é muito mais difícil de controlar e acompanhar, especialmente se você deseja alterar seus pontos de salto com frequência.
Laurence Renshaw
12

Embora outros já tenham esclarecido que não há gotoequivalente direto no bash (e tenham fornecido as alternativas mais próximas, como funções, loops e interrupção), gostaria de ilustrar como o uso de um loop plus breakpode 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é o ping parar de soltar 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
Seth McCauley
fonte
7

Esta solução teve os seguintes problemas:

  • Remove indiscriminadamente todas as linhas de código que terminam em um :
  • Trata em label:qualquer lugar da linha como um rótulo

Aqui está uma shell-checkversão fixa ( limpa):


#!/bin/bash

# GOTO for bash, based upon https://stackoverflow.com/a/31269848/5353461
function goto
{
 local label=$1
 cmd=$(sed -En "/^[[:space:]]*#[[:space:]]*$label:[[:space:]]*#/{:a;n;p;ba};" "$0")
 eval "$cmd"
 exit
}

start=${1:-start}
goto "$start"  # GOTO start: by default

#start:#  Comments can occur after labels
echo start
goto end

  # skip: #  Whitespace is allowed
echo this is usually skipped

# end: #
echo end
Tom Hale
fonte
6

Há mais uma capacidade de obter os resultados desejados: comando trap. Pode ser usado para fins de limpeza, por exemplo.

Serge Roussak
fonte
4

Não há gotono bash.

Aqui está uma solução alternativa suja usando trapqual pula 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

Resultado:

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

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

trapestá usando o tratamento de exceções para obter a alteração no fluxo de código. Nesse caso, trapestá capturando qualquer coisa que faça com que o script EXIT. O comando gotonão existe e, portanto, gera um erro, que normalmente sai do script. Este erro está sendo detectado trape 2>/dev/nulloculta a mensagem de erro que normalmente seria exibida.

Obviamente, essa implementação do goto não é confiável, pois qualquer comando inexistente (ou qualquer outro erro, dessa maneira) executaria o mesmo comando trap. Em particular, você não pode escolher para qual rótulo ir.


Basicamente, no cenário real, você não precisa de nenhuma instrução goto, elas são redundantes, pois chamadas aleatórias para lugares diferentes dificultam a compreensão do seu código.

Se seu código for chamado várias vezes, considere usar loop e altere seu fluxo de trabalho para usar continuee break.

Se o seu código se repetir, considere escrever a função 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 usar a caseinstrução

Se você pode separar seu código longo em partes menores, considere movê-lo para arquivos separados e chame-os a partir do script pai.

kenorb
fonte
Quais são as diferenças entre este formulário e uma função normal?
yurenchen
2
@ yurenchen - Pense trapem usar exception handlingpara conseguir a mudança no fluxo de código. Nesse caso, a armadilha está capturando tudo o que faz com que o script EXITseja acionado, invocando o comando inexistente goto. BTW: O argumento goto trappode ser qualquer coisa, goto ignoredporque é o gotoque causa a EXIT e 2>/dev/nulloculta a mensagem de erro informando que seu script está saindo.
Jesse Chisholm
2

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

Colocar rótulos não escapados no script foi problemático na máquina e causou uma falha. Isso foi fácil de resolver, adicionando # para escapar dos rótulos. Obrigado 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..."
thebunnyrules
fonte
Esta pasta de cópia não está funcionando => ainda há um jumpto.
Alexis Paques
O que isso significa? O que não está funcionando? Você poderia ser mais específico?
thebunnyrules
Claro que tentei o código! Eu testei em 5 ou 6 condições diferentes antes de postar tudo bem-sucedido. Como o código falhou, qual mensagem ou código de erro?
precisa saber é o seguinte
Qualquer homem. Quero ajudá-lo, mas você está sendo muito vago e pouco comunicativo (para não mencionar o insulto à pergunta "você testou")? O que significa "você não colou corretamente"? Se você estiver se referindo ao "/ $ # label #: / {: a; n; p; ba};" parte, eu estou muito ciente de que é diferente. Eu o modifiquei para o novo formato de etiqueta (também conhecido como # label # vs: label em outra resposta que estava travando o script em alguns casos). Você tem algum problema válido com a minha resposta (como falha no script ou sintaxe incorreta) ou simplesmente -1 respondeu a minha resposta porque pensou "Não sei como recortar e colar"?
Thebunnyrules 13/08/2018
1
@thebunnyrules Encontrei o mesmo problema que você, mas o resolvi de maneira diferente em +1 para a sua solução!
Fabby
2

Um simples ir pesquisável para o uso de comentar blocos de código durante a depuração.

GOTO=false
if ${GOTO}; then
    echo "GOTO failed"
    ...
fi # End of GOTO
echo "GOTO done"

O resultado é-> GOTO concluído

ztalbot
fonte
1

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

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

Observe que, como a linha que contém function demoFunctionestá apenas configurando a função, é necessário chamar demoFunctionapós esse script para que a função realmente seja executada.

Você pode adaptar isso facilmente escrevendo várias outras funções e chamando-as se " GOTO" precisar de outro lugar no 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
cutrightjm
fonte