Есть ли в bash оператор "goto"? - PullRequest
177 голосов
/ 09 марта 2012

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

Ответы [ 12 ]

108 голосов
/ 02 августа 2013

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

69 голосов
/ 09 марта 2012

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

43 голосов
/ 07 июля 2015

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

Я обнаружил, что решение Боба Коупленда http://bobcopeland.com/blog/2012/10/goto-in-bash/ элегантно:

#!/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
27 голосов
/ 16 ноября 2012

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

#!/bin/bash

case bar in
  foo)
    echo foo
    ;&

  bar)
    echo bar
    ;&

  *)
    echo star
    ;;
esac

производит:

bar
star
16 голосов
/ 07 августа 2017

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

. Мы также можем предварительно подтвердить это решение, добавив *Команда 1007 * как псевдоним:

#!/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, вам нужно быть осторожным, чтобы код, который вы прыгаете, былt не устанавливает переменные, на которые вы полагаетесь позже - вам может понадобиться переместить эти определения в начало вашего скрипта или чуть выше одного из ваших goto операторов.

11 голосов
/ 27 марта 2016

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

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

4 голосов
/ 18 октября 2018

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

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

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


#!/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
4 голосов
/ 01 августа 2015

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

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

2 голосов
/ 19 декабря 2018

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

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

Результат is-> GOTO done

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...