Есть ли инструкция "goto" в bash?

Есть ли инструкция "goto" в bash? Я знаю, что это считается плохой практикой, но мне нужно конкретно "перейти".

Ответ 1

Нет, нет; см. & sect; 3.2.4 "Составные команды" в справочном руководстве Bash для получения информации о существующих структурах управления. В частности, обратите внимание на упоминание break и continue, которые не так гибки, как goto, но более гибкие в Bash, чем на некоторых языках, и могут помочь вам достичь того, чего вы хотите. (Что бы вы ни хотели...)

Ответ 2

Если вы используете его, чтобы пропустить часть большого script для отладки (см. комментарий Karl Nicoll), тогда если false может быть хорошим вариантом (не уверен, что "false" всегда доступно, для меня оно находится в/бен/ложь):

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

if false; then

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

fi

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

Трудность возникает, когда нужно вырвать код отладки. Конструкция "if false" довольно проста и запоминаема, но как вы находите подходящую цифру? Если ваш редактор позволяет блокировать отступ, вы можете отступать от пропущенного блока (тогда вы захотите вернуть его, когда закончите). Или комментарий к фину, но это должно быть то, что вы запомните, что, я подозреваю, будет очень зависимым от программиста.

Ответ 3

Это действительно может быть полезно для некоторых отладочных или демонстрационных нужд.

Я обнаружил, что решение Боба Коупленда 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

результаты в:

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

Ответ 4

Вы можете использовать case в bash для имитации goto:

#!/bin/bash

case bar in
  foo)
    echo foo
    ;&

  bar)
    echo bar
    ;&

  *)
    echo star
    ;;
esac

дает:

bar
star

Ответ 5

Если вы тестируете/отлаживаете bash script и просто хотите пропустить вперед один или несколько разделов кода, вот очень простой способ сделать это, что также очень легко найти и удалить позже (в отличие от большинства методов, описанных выше).

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

Чтобы вернуть ваш script в нормальное состояние, просто удалите любые строки с помощью GOTO.

Мы также можем предусмотреть это решение, добавив команду GOTO в качестве псевдонима:

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

Псевдонимы обычно не работают в сценариях bash, поэтому нам нужна команда shopt, чтобы исправить это.

Если вы хотите включить/отключить GOTO, нам нужно немного больше:

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

Затем вы можете сделать export DEBUG=TRUE перед запуском script.

Этикетки являются комментариями, поэтому не будут вызывать синтаксические ошибки, если отключить наш GOTO (установив GOTO в ':' no-op), но это означает, что нам нужно процитировать их в нашей GOTO.

Всякий раз, когда вы используете какое-либо решение GOTO, вам нужно быть осторожным, чтобы код, который вы проскакиваете мимо, не устанавливает какие-либо переменные, на которые вы полагаетесь позже - вам может потребоваться переместить эти определения в начало вашего script или чуть выше одного из ваших операторов GOTO.

Ответ 6

Хотя другие уже выяснили, что нет прямого эквивалента goto в bash (и предоставлены самые близкие альтернативы, такие как функции, циклы и разрыв), я хотел бы проиллюстрировать, как использование цикла plus break может моделировать определенный тип инструкции goto.

Ситуация, когда я считаю это наиболее полезной, - это когда мне нужно вернуться к началу раздела кода, если определенные условия не выполняются. В приведенном ниже примере цикл while будет работать вечно до тех пор, пока ping не перестанет удалять пакеты на тестовый IP.

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

Ответ 7

Есть еще одна возможность добиться желаемых результатов: команда trap. Например, он может использоваться для очистки.

Ответ 8

В bash нет goto.

Вот какое-то грязное обходное решение, использующее trap, который перескакивает только назад:)

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

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

Вывод:

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

Это не должно использоваться таким образом, но только в образовательных целях. Вот почему это работает:

trap использует обработку исключений для достижения изменения в потоке кода. В этом случае trap ловит все, что вызывает script EXIT. Команда goto не существует и, следовательно, выдает ошибку, которая обычно выходила бы из script. Эта ошибка ломается с помощью trap, а 2>/dev/null скрывает сообщение об ошибке, которое обычно отображается.

Эта реализация goto, очевидно, не является надежной, поскольку любая несуществующая команда (или любая другая ошибка для этого) будет выполнять ту же команду trap. В частности, вы не можете выбрать, на какую метку перейти.


В основном в реальном сценарии вам не нужны какие-либо инструкции goto, они избыточны, так как случайные звонки в разные места только затрудняют понимание вашего кода.

Если ваш код вызывается много раз, рассмотрим использование цикла и изменение его рабочего процесса для использования continue и break.

Если ваш код повторяется сам, подумайте о том, как писать функцию и вызывать ее столько раз, сколько хотите.

Если вашему коду нужно перейти в конкретный раздел на основе значения переменной, рассмотрите с помощью инструкции case.

Если вы можете разделить свой длинный код на более мелкие части, подумайте о переносе его в отдельные файлы и вызовите их из родительского script.

Ответ 9

Я нашел способ сделать это с помощью функций.

Скажем, например, у вас есть 3 варианта: A, B и C. A и B выполните команду, но C предоставит вам больше информации и снова вернет вас в исходное приглашение. Это можно сделать с помощью функций.

Обратите внимание, что поскольку строка, контактирующая с function demoFunction, просто устанавливает эту функцию, вам нужно вызвать demoFunction после этого script, чтобы функция действительно выполнялась.

Вы можете легко адаптировать это, написав несколько других функций и называя их, если вам нужно "GOTO" другое место в вашей оболочке script.

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

Ответ 10

Это небольшое исправление сценария Джуди Шмидт, созданного Хабббитом.

Помещение в сценарий неэкранированных меток было проблематично на компьютере и приводило к его аварийному завершению. Это было достаточно легко решить, добавив #, чтобы экранировать метки. Спасибо Алексею Магуре и access_granted за их предложения.

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

Ответ 11

Это решение имело следующие проблемы:

  • Без разбора удаляет все строки кода, заканчивающиеся на :
  • Обрабатывает label: любом месте строки как ярлык

Вот фиксированная ( shell-check clean) версия:


#!/bin/bash

# GOTO for bash, based upon /questions/35977/is-there-a-goto-statement-in-bash/262112#262112
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

Ответ 12

Простой способ поиска с возможностью комментирования блоков кода при отладке.

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

Результат is-> GOTO сделано