¿Hay una declaración de "goto" en bash?


¿Hay una declaración "goto" en bash ? Sé que se considera una mala práctica, pero necesito específicamente "goto".

Author: glglgl, 2012-03-09

10 answers

No, no lo hay; véase §3.2.4 "Comandos compuestos" en el Manual de Referencia de Bash para obtener información sobre las estructuras de control que existen. En particular, tenga en cuenta la mención de break y continue, que no son tan flexibles como goto, pero son más flexibles en Bash que en algunos idiomas, y pueden ayudarlo a lograr lo que desea. (Lo que sea que quieras . . .)

 62
Author: ruakh,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-03-09 18:31:49

Si lo está usando para omitir parte de un script grande para la depuración (vea el comentario de Karl Nicoll), entonces si false podría ser una buena opción (no estoy seguro de si "false" está siempre disponible, para mí está en /bin/false):

# ... Code I want to run here ...

if false; then

# ... Code I want to skip here ...

fi

# ... I want to resume here ...

La dificultad aparece cuando es el momento de extraer su código de depuración. La construcción "if false" es bastante sencilla y memorable, pero ¿cómo encuentras la fi correspondiente? Si tu editor te permite bloquear la sangría, podrías sangrar el bloque omitido (entonces querrás para ponerlo de nuevo cuando haya terminado). O un comentario en la línea fi, pero tendría que ser algo que recordarán, que sospecho que será muy dependiente del programador.

 90
Author: Michael Rusch,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-08-02 14:31:50

De hecho puede ser útil para algunas necesidades de depuración o demostración.

Encontré que la solución de 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

Resultados en:

$ ./test.sh
x is 100
$ ./test.sh foo
x is 10
$ ./test.sh mid
This is not printed!
x is 101
 34
Author: Hubbitus,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-04-23 16:46:21

Puedes usar case en bash para simular un goto:

#!/bin/bash

case bar in
  foo)
    echo foo
    ;&

  bar)
    echo bar
    ;&

  *)
    echo star
    ;;
esac

Produce:

bar
star
 24
Author: Paul Brannan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-11-16 19:33:26

Si está probando/depurando un script bash, y simplemente desea saltar hacia adelante más allá de una o más secciones de código, aquí hay una forma muy simple de hacerlo que también es muy fácil de encontrar y eliminar más tarde (a diferencia de la mayoría de los métodos descritos anteriormente).

#!/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 poner su script de vuelta a la normalidad, simplemente elimine cualquier línea con GOTO.

También podemos embellecer esta solución, agregando un comando goto como un 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"

Los alias no suelen funcionar en scripts bash, así que necesita el comando shopt para arreglar eso.

Si quieres poder activar / desactivar tus goto, necesitamos un poco más:

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

Entonces puedes hacer export DEBUG=TRUE antes de ejecutar el script.

Las etiquetas son comentarios, por lo que no causarán errores de sintaxis si deshabilita nuestras goto's (estableciendo goto a la ':' no-op), pero esto significa que necesitamos citarlas en nuestras goto sentencias.

Siempre que use cualquier tipo de solución goto, debe tener cuidado de que el código que saltar más allá no establece ninguna variable en la que confíe más adelante; es posible que deba mover esas definiciones a la parte superior de su script, o justo encima de una de sus sentencias goto.

 11
Author: Laurence Renshaw,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-08-07 01:23:17

Aunque otros ya han aclarado que no hay un equivalente directo goto en bash (y proporcionaron las alternativas más cercanas como funciones, bucles y break), me gustaría ilustrar cómo el uso de un bucle más break puede simular un tipo específico de sentencia goto.

La situación en la que encuentro esto más útil es cuando necesito volver al principio de una sección de código si no se cumplen ciertas condiciones. En el ejemplo a continuación, el bucle while se ejecutará para siempre hasta ping deja de soltar paquetes a una IP de prueba.

#!/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
 10
Author: Seth McCauley,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-06-13 23:55:26

Hay una habilidad más para lograr los resultados deseados: comando trap. Se puede utilizar para fines de limpieza, por ejemplo.

 6
Author: Serge Roussak,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-06-17 12:29:50

No hay goto en bash.

Aquí hay una solución sucia usando trap que salta solo hacia atrás:)

#!/bin/bash -e
trap '
echo I am
sleep 1
echo here now.
' EXIT

echo foo
goto trap 2> /dev/null
echo bar

Salida:

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

Esto no debe usarse de esa manera, sino solo con fines educativos. He aquí por qué esto funciona:

trap está utilizando el manejo de excepciones para lograr el cambio en el flujo de código. En este caso, trap está capturando cualquier cosa que haga que el script SALGA. El comando goto no existe, y por lo tanto lanza un error, lo que normalmente saldría del guión. Este error es ser atrapado con trap, y 2>/dev/null oculta el mensaje de error que normalmente se muestran.

Esta implementación de goto obviamente no es confiable, ya que cualquier comando inexistente (o cualquier otro error, de esa manera), ejecutaría el mismo comando trap. En particular, no puede elegir a qué etiqueta ir.


Básicamente en el escenario real no necesita ninguna declaración goto, son redundantes como aleatorias las llamadas a diferentes lugares solo hacen que su código sea difícil de entender.

Si su código es invocado muchas veces, entonces considere usar loop y cambiar su flujo de trabajo para usar continue y break.

Si su código se repite, considere escribir la función y llamarla tantas veces como desee.

Si su código necesita saltar a una sección específica basada en el valor de la variable, considere usar la instrucción case.

Si puede separar su código largo en piezas más pequeñas, considere moverlo a archivos separados y llámelos desde el script padre.

 3
Author: kenorb,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-07-28 18:51:18

Encontré una manera de hacer esto usando funciones.

Digamos, por ejemplo, que tienes 3 opciones: A, B, y C. A y B ejecutan un comando, pero C te da más información y te lleva al prompt original de nuevo. Esto se puede hacer usando funciones.

Tenga en cuenta que ya que la línea containg function demoFunction está configurando la función, necesita llamar a demoFunction después de ese script para que la función realmente se ejecute.

Puede adaptar esto fácilmente escribiendo múltiples otras funciones y llamarlas si necesita " GOTO" otro lugar en su 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
 1
Author: cutrightjm,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-12-26 21:18:49

Esta es una pequeña corrección del guion de Judy Schmidt presentado por Hubbbitus.

Poner etiquetas no escapadas en el script fue problemático en la máquina y causó que se bloqueara. Esto fue bastante fácil de resolver añadiendo # para escapar de las etiquetas. Gracias a Alexej Magura y access_granted por sus sugerencias.

#!/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..."
 0
Author: thebunnyrules,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-09-01 03:11:03