Есть ли инструкция "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 сделано