Чи є в bash твердження "goto"?


204

Чи є в bash твердження "goto"? Я знаю, що це вважається поганою практикою, але мені потрібно конкретно "гото".


4
Ні, немає gotoв баші (принаймні, це говорить command not foundдля мене). Чому? Ймовірно, є кращий спосіб це зробити.
Ніклас Б.

153
У нього можуть бути свої причини. Я знайшов це запитання, тому що я хочу, щоб gotoзаява пропускала багато коду для налагодження великого сценарію, не чекаючи години для завершення різних непов'язаних завдань. Я, звичайно, не використовував би gotoу виробничому коді, але для налагодження мого коду це зробило б моє життя нескінченно простішим, і його було б легше помітити, коли потрібно було його видалити.
Карл Ніколл

30
@delnan Але відсутність goto може зробити деякі речі складнішими. Дійсно є випадки використання.
glglgl

71
Мені нудить цей міф про Гото! У Goto немає нічого поганого! Все, що ви пишете, з часом стає goto. У асемблері є лише goto. Хорошим приводом для використання goto у вищих мовах програмування є, наприклад, вискакування вкладених циклів чистим і читабельним способом.
Олександр Чутро

25
"Уникайте гото" - чудове правило. Як і будь-яке правило, його слід вивчати в три етапи. По-перше: дотримуйтесь правила, поки це не буде другою природою. По-друге, навчіться розуміти причини правила. По-третє, вивчіть хороші винятки з правила, ґрунтуючись на всебічному розумінні того, як слід керуватись правилом та причини цього правила. Уникайте пропускання вище кроків, що було б як "goto". ;-)
Джефф Ліверман

Відповіді:


75

Ні, немає; см §3.2.4 «Складові команди» в Bash Reference Manual для отримання інформації про структури управління , які роблять існування. Зокрема, зверніть увагу на згадку про breakта continue, які не такі гнучкі, як goto, але є більш гнучкими в Bash, ніж на деяких мовах, і можуть допомогти вам досягти того, що ви хочете. (Що б там не хотілося.)


10
Не могли б ви розширити питання про "більш гнучку мову в Bash, ніж на деяких мовах"?
user239558

21
@ user239558: Деякі мови дозволяють вам breakабо continueз внутрішнього циклу, тоді як Bash дозволяє вказати, скільки рівнів циклу для переходу. (І навіть з мов, які дозволяють вам breakабо continueз довільних циклів, більшість вимагає, щоб їх було виражено статично - наприклад, break foo;вирветься з позначення циклу foo- тоді як у Bash це виражається динамічно - наприклад, break "$foo"вирветься з $fooциклів. )
ruakh

Я рекомендую визначити функції з необхідними функціями та викликати їх з інших частин коду.
blmayer

@ruakh Насправді багато мов дозволяють break LABEL_NAME;, а не огидно Баша break INTEGER;.
Sapphire_Brick

1
@Sapphire_Brick: Так, я це згадував у коментарі, на який ви відповідаєте.
ruakh

124

Якщо ви використовуєте його, щоб пропустити частину великого сценарію для налагодження (див. Коментар Карла Ніколя), тоді, якщо 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 ...

Складність виникає, коли настав час зірвати код налагодження. Конструкт "якщо помилково" досить простий і запам’ятовується, але як ви можете знайти відповідне фі? Якщо ваш редактор дозволяє блокувати відступ, ви можете відступити пропущений блок (тоді ви захочете повернути його, коли закінчите). Або коментар до рядка fi, але це повинно бути чимось, що ви запам'ятаєте, і, як я підозрюю, буде дуже залежним від програмування.


6
Так false, завжди є. Але якщо у вас є код коду, який ви не хочете виконувати, просто прокоментуйте його. Або видаліть його (і подивіться у вашій системі управління джерелом, якщо вам потрібно буде відновити її пізніше).
Кіт Томпсон

1
Якщо блок код коду занадто довгий, щоб нудно коментувати один рядок за одним, дивіться ці хитрощі. stackoverflow.com/questions/947897/… Однак, вони також не допомагають текстовому редактору відповідати початку та кінцю, тому вони не дуже покращають.
Camille Goudeseune

4
"if false" часто набагато кращий, ніж коментування коду, оскільки він забезпечує, що доданий код продовжує залишатися юридичним кодом. Найкращим приводом для коментування коду є те, коли його дійсно потрібно видалити, але є щось, що потрібно пам’ятати - тож це просто коментар, а вже не «код».
Джефф Лірман

1
Я використовую коментар '#if false;'. Таким чином я можу просто знайти це і знайти початок і кінець розділу видалення налагодження.
Jon

Цю відповідь не слід підтримувати, оскільки вона жодним чином не відповідає на питання ОП.
Віктор

54

Це дійсно може бути корисним для деяких налагоджень чи демонстраційних потреб.

Я виявив, що рішення Bob Copeland 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

36
"Моє прагнення зробити баш виглядати так, що мова складання стає все ближче до завершення." - Ого. Просто, вау.
Брайан Агнеу

5
Єдине, що я хотів би змінити, це зробити так, щоб мітки починалися так, : start:щоб вони не були синтаксичними помилками.
Олексій Магура

5
Було б краще, якби ви зробили це: cmd = $ (sed -n "/ # $ label: / {: a; n; p; ba};" $ 0 | grep -v ': $') з мітками, що починаються з: # start: => це дозволить запобігти помилкам скрипту
access_granted

2
Гм, справді це, швидше за все, моя помилка. У мене є повідомлення про редагування. Дякую.
Губбіт

2
set -xдопомагає зрозуміти, що відбувається
Джон Лін

30

Ви можете використовувати casebash для імітації goto:

#!/bin/bash

case bar in
  foo)
    echo foo
    ;&

  bar)
    echo bar
    ;&

  *)
    echo star
    ;;
esac

виробляє:

bar
star

4
Зауважте, що для цього потрібно bash v4.0+. Однак це не загальний ціль, gotoа пропускний варіант caseзаяви.
mklement0

3
я думаю, це має бути відповіддю. У мене є справжня потреба перейти, щоб підтримати відновлення виконання сценарію, з даної інструкції. це, в усьому, але семантичний, гото, семантичний і синтаксичний цукри милі, але не суворо необхідні. чудове рішення, ІМО.
Натан г

1
@nathang, будь то відповідь залежить від того, чи відбувається ваш випадок до сітки з підмножиною загального випадку ОП запитав о. На жаль, питання задає загальну справу, що робить цю відповідь занадто вузькою, щоб бути правильною. (Чи варто це питання закрити як занадто широке з цієї причини - це інше обговорення).
Чарльз Даффі

1
goto - це більше, ніж вибір. функція goto означає, що можна переходити до місць, що відповідають певним умовам, створюючи навіть цикл, як потік ...
Серхіо Абреу

19

Якщо ви тестуєте / налагоджуєте баш-скрипт і просто хочете пропустити вперед один або кілька розділів коду, ось дуже простий спосіб зробити це, який також дуже легко знайти та видалити пізніше (на відміну від більшості методів описано вище).

#!/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на " :" неоперативний), але це означає, що нам потрібно цитувати їх у своїх gotoвисловлюваннях.

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


Не вникаючи в добру / погану властивість гото, рішення Лоренса просто класне. Ви отримали мій голос.
Mogens TrasherDK

1
Це більше схоже на пропуск, if(false)здається, має більше сенсу (для мене).
vesperto

2
Цитування goto "label" також означає, що тут-doc не розширюється / оцінюється, що позбавляє вас від деяких важко знайти помилок (без котирування, це може бути предметом: розширення параметрів, підміна команд та арифметичне розширення, яке все може мати побічні ефекти). Ви також можете трохи підправити це alias goto=":<<"і catвзагалі не відмовитися .
mr.spuratic

так, Vesperto, ви можете використовувати оператор 'if', і я це робив у багатьох сценаріях, але він стає дуже безладним і набагато важче контролювати і слідкувати, особливо якщо ви хочете часто змінювати свої точки стрибків.
Лоренс Реншо

12

Хоча інші вже уточнили, що gotoв bash немає прямого еквівалента (і надаються найближчі альтернативи, такі як функції, цикли та розриви), я хотів би проілюструвати, як за допомогою циклу плюс 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

Це рішення мало наступні проблеми:

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

6

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


4

У 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, очевидно, не є надійною, оскільки будь-яка неіснуюча команда (або будь-яка інша помилка для цього способу) виконувала б ту саму команду trap. Зокрема, ви не можете вибрати, до якої мітки перейти.


В основному в реальному сценарії вам не потрібні будь-які заяви goto, вони надлишкові, оскільки випадкові дзвінки в різні місця лише ускладнюють ваш код.

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

Якщо ваш код повторюється самостійно, подумайте про те, як написати функцію та викликати її стільки разів, скільки вам потрібно.

Якщо ваш код потребує переходу до конкретного розділу на основі значення змінної, тоді подумайте про використання caseоператора.

Якщо ви можете відокремити довгий код на менші шматочки, подумайте про переміщення його в окремі файли та зателефонуйте їм із батьківського сценарію.


які відмінності між цією формою та нормальною функцією?
yurenchen

2
@yurenchen - Подумайте, trapяк використовувати exception handlingдля досягнення зміни потоку коду. У цьому випадку пастка вловлює все, що викликає скрипт EXIT, що ініціюється за допомогою виклику неіснуючої команди goto. BTW: Аргументом goto trapможе бути все goto ignoredщо завгодно, тому що саме це gotoвикликає EXIT, а 2>/dev/nullприховує повідомлення про помилку, яке говорить про те, що ваш сценарій закінчується.
Джессі Чизгольм

2

Це невелика корекція сценарію Джуді Шмідт, викладеного Губбітом.

Поставлення невирішених міток у сценарії було проблематичним на машині і призвело до його аварії. Це було досить легко вирішити, додавши #, щоб уникнути міток. Дякуємо Олексію Магура та 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..."

Ця копіювальна паста не працює => все ще є стрибок.
Алексіс Пакес

Що це означає? Що не працює? Чи можете ви бути більш конкретними?
thebunnyrules

Звичайно, я спробував код! Я проходив тестування в 5 або 6 різних умовах, перш ніж опублікувати всі успішні. Як код не вдався до вас, яке повідомлення про помилку чи код?
thebunnyrules

Яка б людина не була. Я хочу вам допомогти, але ви насправді розпливчасті та некомунікабельні (не кажучи вже про образливість питання "ти це перевірив"). Що означає "ви не правильно вставили"? Якщо ви маєте на увазі "/ $ # label #: / {: a; n; p; ba};" частина, я дуже усвідомлюю, що це інакше. Я змінив його на новий формат міток (він же # label # vs: label в іншій відповіді, який в деяких випадках виявився збоєм сценарію). Чи є у вас дійсна проблема з моєю відповіддю (наприклад, збій сценарію чи поганий синтаксис) чи ви просто -1 мою відповідь, оскільки ви думали, що "я не знаю, як вирізати та вставити"?
thebunnyrules

1
@thebunnyrules Я зіткнувся з тим же питанням, що і ви, але вирішив це по-іншому +1 для вашого рішення!
Fabby

2

Просте goto для пошуку для використання коментування кодових блоків під час налагодження.

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

Результат -> GOTO зроблено


1

Я знайшов спосіб зробити це за допомогою функцій.

Скажімо, наприклад, у вас є 3 варіанти: A, Bі C. Aі Bвиконайте команду, але Cнадає більше інформації та знову поверне вас до початкового запиту. Це можна зробити за допомогою функцій.

Зауважте, що оскільки лінія, що контактує function demoFunction, лише налаштовує функцію, вам потрібно зателефонувати demoFunctionпісля цього сценарію, щоб ця функція фактично запускалася.

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

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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.