it-swarm.com.ru

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

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

168
kofucii

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

63
ruakh

Если вы используете его, чтобы пропустить часть большого скрипта для отладки (см. Комментарий Карла Николла), тогда, если false может быть хорошим вариантом (не уверен, всегда ли доступен «false», для меня он находится в/bin/false). :

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

if false; then

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

fi

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

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

101
Michael Rusch

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

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

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

#!/bin/bash

case bar in
  foo)
    echo foo
    ;&

  bar)
    echo bar
    ;&

  *)
    echo star
    ;;
esac

производит:

bar
star
26
Paul Brannan

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

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

Чтобы вернуть скрипт в нормальное состояние, просто удалите все строки с помощью 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 перед запуском скрипта.

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

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

15
Laurence Renshaw

Хотя другие уже пояснили, что в bash нет прямого эквивалента goto (и предоставили наиболее близкие альтернативы, такие как функции, циклы и разрыв), я хотел бы проиллюстрировать, как использование цикла плюс 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
11
Seth McCauley

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

6
Serge Roussak

В 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 перехватывает все, что приводит к выходу скрипта. Команда goto не существует и, следовательно, выдает ошибку, которая обычно завершает работу сценария. Эта ошибка обнаруживается с помощью trap, а 2>/dev/null скрывает сообщение об ошибке, которое обычно отображается.

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


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

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

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

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

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

3
kenorb

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

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

Вот исправленная ( Shell-check clean) версия:


#!/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
1
Tom Hale

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

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

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

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

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

Вы можете легко адаптировать это, написав несколько других функций и вызывая их, если вам нужно «GOTO» другое место в вашем скрипте 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
cutrightjm

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

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

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

0
ztalbot