Есть ли в bash оператор «goto»?

156

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

  • 4
    Нет, в bash нет goto (по крайней мере, там написано, что command not found для меня). Зачем? Скорее всего, есть лучший способ сделать это.
  • 4
    Вам никогда не нужно идти, в худшем случае вам нужно больше практиковаться с другими инструментами управления потоком.
Показать ещё 10 комментариев
Теги:
goto

12 ответов

59
Лучший ответ

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

  • 7
    Не могли бы вы рассказать о «более гибком в Bash, чем в некоторых языках»?
  • 18
    @ user239558: Некоторые языки позволяют вам break или continue самый внутренний цикл, в то время как Bash позволяет вам указать, сколько уровней цикла нужно перейти. (И даже из языков, которые позволяют вам break или continue произвольные циклы, большинство требует, чтобы это выражалось статически - например, break foo; будет выходить из цикла с пометкой foo - тогда как в Bash это выражается динамически - например, break "$foo" вырвется из циклов $foo .)
92

Если вы используете его, чтобы пропустить часть большого 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" довольно проста и запоминаема, но как вы находите подходящую цифру? Если ваш редактор позволяет блокировать отступ, вы можете отступать от пропущенного блока (тогда вы захотите вернуть его, когда закончите). Или комментарий к фину, но это должно быть то, что вы запомните, что, я подозреваю, будет очень зависимым от программиста.

  • 6
    Да, false всегда доступна. Но если у вас есть блок кода, который вы не хотите выполнять, просто закомментируйте его. Или удалите его (и посмотрите в вашей системе контроля версий, если вам нужно восстановить его позже).
  • 1
    Если этот блок кода слишком длинный, чтобы утомительно комментировать одну строку за раз, посмотрите эти приемы. stackoverflow.com/questions/947897/… Тем не менее, они не помогают текстовому редактору сопоставить начало и конец, так что они не слишком улучшаются.
Показать ещё 2 комментария
36

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

Я обнаружил, что решение Боба Коупленда 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
  • 26
    «Мое стремление сделать bash похожим на ассемблер приближается к завершению». - Вот это да. Просто вау.
  • 5
    единственное, что я хотел бы изменить, это сделать так, чтобы метки начинались так : start: чтобы они не были синтаксическими ошибками.
Показать ещё 10 комментариев
24

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

#!/bin/bash

case bar in
  foo)
    echo foo
    ;&

  bar)
    echo bar
    ;&

  *)
    echo star
    ;;
esac

дает:

bar
star
  • 4
    Обратите внимание, что для этого требуется bash v4.0+ . Это, однако, не универсальный goto а альтернативный вариант для описания case .
  • 3
    Я думаю, что это должен быть ответ. мне действительно нужно перейти для поддержки возобновления выполнения скрипта из данной инструкции. это, во всех отношениях, но семантика, goto, и семантика и синтаксические сахара симпатичны, но не строго необходимы. отличное решение, ИМО.
Показать ещё 2 комментария
14

Если вы тестируете/отлаживаете 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.

  • 0
    Не вдаваясь в хорошие / плохие идеи Goto, решение Лоуренса просто круто. Вы получили мой голос.
  • 1
    Это больше похоже на переход, if(false) кажется, имеет больше смысла (для меня).
Показать ещё 2 комментария
11

Хотя другие уже выяснили, что нет прямого эквивалента 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
6

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

3

В 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.

  • 0
    в чем разница между этой формой и нормальной функцией?
  • 2
    @yurenchen - Думайте о trap как об использовании exception handling для достижения изменений в потоке кода. В этом случае ловушка перехватывает все, что вызывает сценарий EXIT , что вызывается при вызове несуществующей команды goto . Кстати: аргумент goto trap может быть любым, goto ignored потому что именно goto вызывает EXIT, а 2>/dev/null скрывает сообщение об ошибке, сообщающее, что ваш скрипт завершается.
1

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

  • Без разбора удаляет все строки кода, заканчивающиеся на :
  • Обрабатывает 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
1

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

Помещение в сценарий неэкранированных меток было проблематично на компьютере и приводило к его аварийному завершению. Это было достаточно легко решить, добавив #, чтобы экранировать метки. Спасибо Алексею Магуре и 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..."
  • 0
    Эта копия вставки не работает => есть еще прыжок.
  • 0
    Что это значит? Что не работает? Не могли бы Вы уточнить?
Показать ещё 10 комментариев
1

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

Скажем, например, у вас есть 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
0

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

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

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

Ещё вопросы

Сообщество Overcoder
Наверх
Меню