Czy w bash jest napis „goto”?

204

Czy w bash jest napis „goto”? Wiem, że jest to uważane za złą praktykę, ale potrzebuję właśnie „goto”.

kofucii
źródło
4
Nie, nie ma gotobashu (przynajmniej tak command not foundmi mówi ). Czemu? Są szanse, że istnieje lepszy sposób, aby to zrobić.
Niklas B.
153
Może mieć swoje powody. Znalazłem to pytanie, ponieważ chcę, aby gotoinstrukcja pominęła dużo kodu do debugowania dużego skryptu bez czekania godziny na wykonanie różnych niezwiązanych zadań. Na pewno nie gotoużywałbym kodu w produkcji, ale do debugowania mojego kodu uczyniłoby to moje życie nieskończenie łatwiejszym i łatwiej byłoby zauważyć, kiedy trzeba go usunąć.
Karl Nicoll,
30
@delnan Ale brak goto może skomplikować niektóre sprawy. Rzeczywiście istnieją przypadki użycia.
glglgl
71
Mam dość tego mitu goto! Z goto nie ma nic złego! Wszystko, co piszesz, staje się w końcu goto. W asemblerze jest tylko goto. Dobrym powodem do użycia goto w wyższych językach programowania jest na przykład wyskakiwanie z zagnieżdżonych pętli w czysty i czytelny sposób.
Alexander Czutro
25
„Unikaj goto” to świetna zasada. Jak każda zasada, należy się tego nauczyć w trzech etapach. Po pierwsze: postępuj zgodnie z regułą, aż będzie to druga natura. Po drugie, naucz się rozumieć przyczyny tej reguły. Po trzecie, poznaj dobre wyjątki od reguły, opierając się na kompleksowym zrozumieniu, jak stosować się do reguły i jej przyczyn. Unikaj pomijania powyższych kroków, co byłoby jak użycie „goto”. ;-)
Jeff Learman

Odpowiedzi:

75

Nie, nie ma; patrz §3.2.4 „związek Polecenia” w bash Reference Manual informacji na temat struktur kontrolnych, które zrobienia istnieje. W szczególności zwróć uwagę na wzmiankę o breaki continue, które nie są tak elastyczne jak goto, ale są bardziej elastyczne w Bash niż w niektórych językach, i mogą pomóc ci osiągnąć to, co chcesz. (Cokolwiek chcesz…)

ruakh
źródło
10
Czy możesz rozwinąć „bardziej elastyczny w Bash niż w niektórych językach”?
user239558
21
@ user239558: Niektóre języki pozwalają tylko na breaklub continuez najbardziej wewnętrznej pętli, podczas gdy Bash pozwala określić, ile poziomów pętli należy przeskoczyć. (A nawet w przypadku języków, które pozwalają na dowolne pętle breaklub continuez nich, większość wymaga wyrażenia statycznego - np. break foo;Wyrwie się z pętli oznaczonej foo- podczas gdy w języku Bash jest wyrażone dynamicznie - np. break "$foo"Wyrwie się z $foopętli. )
ruakh
Polecam zdefiniowanie funkcji za pomocą potrzebnych funkcji i skalowanie ich z innych części kodu.
blmayer
@ruakh W rzeczywistości pozwala na to wiele języków break LABEL_NAME;, a nie obrzydliwość Basha break INTEGER;.
Sapphire_Brick
1
@Sapphire_Brick: Tak, wspomniałem o tym w komentarzu, na który odpowiadasz.
ruakh
124

Jeśli używasz go do pominięcia części dużego skryptu do debugowania (patrz komentarz Karla Nicolla), to jeśli fałsz może być dobrą opcją (nie jestem pewien, czy „fałsz” jest zawsze dostępny, dla mnie jest w / bin / false) :

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

if false; then

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

fi

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

Trudność pojawia się, gdy nadszedł czas, aby wyrwać kod debugowania. Konstrukcja „jeśli fałszywa” jest dość prosta i niezapomniana, ale jak znaleźć pasujące fi? Jeśli twój edytor pozwala ci zablokować wcięcie, możesz wciąć pominięty blok (wtedy będziesz chciał go odłożyć z powrotem po zakończeniu). Lub komentarz do linii fi, ale musiałoby to być coś, co zapamiętasz, co, jak podejrzewam, będzie bardzo zależne od programisty.

Michael Rusch
źródło
6
Tak, falsejest zawsze dostępne. Ale jeśli masz blok kodu, którego nie chcesz wykonać, po prostu go skomentuj. Lub usuń go (i zajrzyj do systemu kontroli źródła, jeśli chcesz go później odzyskać).
Keith Thompson,
1
Jeśli blok kodu jest zbyt długi, aby żmudnie komentować jedną linię na raz, zobacz te sztuczki. stackoverflow.com/questions/947897/... Jednak nie pomagają one również edytorowi tekstowemu dopasować początku do końca, więc nie stanowią większego ulepszenia.
Camille Goudeseune,
4
„jeśli fałsz” jest często o wiele lepszy niż komentowanie kodu, ponieważ zapewnia, że ​​załączony kod jest nadal legalnym kodem. Najlepszym usprawiedliwieniem do komentowania kodu jest to, kiedy naprawdę trzeba go usunąć, ale jest coś, o czym należy pamiętać - więc jest to tylko komentarz, a nie „kod”.
Jeff Learman
1
Używam komentarza „#if false;”. W ten sposób mogę po prostu to wyszukać i znaleźć zarówno początek, jak i koniec sekcji usuwania debugowania.
Jon
Nie należy głosować nad odpowiedzią, ponieważ w żaden sposób nie odpowiada ona na pytanie PO.
Viktor Joras,
54

Może to być przydatne w przypadku niektórych potrzeb związanych z debugowaniem lub demonstracją.

Odkryłem, że rozwiązanie Boba Copelanda 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

prowadzi do:

$ ./test.sh
x is 100
$ ./test.sh foo
x is 10
$ ./test.sh mid
This is not printed!
x is 101
Hubbitus
źródło
36
„Moje dążenie do tego, aby bash wyglądał jak język asemblera, zbliża się do końca” - Łał. Wow.
Brian Agnew
5
jedyne, co bym zmienił, to tak, aby etykiety zaczynały się tak : start:, aby nie były błędami składniowymi.
Alexej Magura
5
Byłoby lepiej, gdybyś to zrobił: cmd = $ (sed -n "/ # $ label: / {: a; n; p; ba};" $ 0 | grep -v ': $') z etykietami zaczynającymi się jako: # start: => zapobiegnie to błędom skryptu
access_granted
2
Hm, rzeczywiście to najprawdopodobniej mój błąd. Mam edytowany post. Dziękuję Ci.
Hubbitus
2
set -xpomaga zrozumieć, co się dzieje
John Lin
30

Możesz użyć casew bash do symulacji goto:

#!/bin/bash

case bar in
  foo)
    echo foo
    ;&

  bar)
    echo bar
    ;&

  *)
    echo star
    ;;
esac

produkuje:

bar
star
Paul Brannan
źródło
4
Pamiętaj, że to wymaga bash v4.0+. Nie jest to jednak gotoopcja ogólnego przeznaczenia, ale opcja zastępcza dla caseoświadczenia.
mklement0
3
myślę, że taka powinna być odpowiedź. mam prawdziwą potrzebę, aby przejść do wznowienia wykonywania skryptu, z danej instrukcji. jest to pod każdym względem oprócz semantycznego, goto, a semantyka i cukry składniowe są urocze, ale nie są absolutnie konieczne. świetne rozwiązanie, IMO.
nathan g
1
@nathang, czy to Odpowiedź zależy od tego, czy sprawa dzieje się z siatki z podzbioru ogólnym przypadku PO poprosił o. Niestety pytanie dotyczy ogólnego przypadku, w wyniku czego odpowiedź jest zbyt wąska, aby była poprawna. (To, czy to pytanie powinno być zamknięte z powodu zbyt szerokiego zakresu, to inna dyskusja).
Charles Duffy,
1
goto to coś więcej niż wybieranie. funkcja goto oznacza możliwość przeskakiwania do miejsc zgodnie z określonymi warunkami, tworząc nawet pętlę podobną do przepływu ...
Sergio Abreu
19

Jeśli testujesz / debugujesz skrypt bash i po prostu chcesz pominąć jedną lub więcej sekcji kodu, oto bardzo prosty sposób, aby to zrobić, a także bardzo łatwo go znaleźć i usunąć później (w przeciwieństwie do większości metod opisane powyżej).

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

Aby przywrócić skrypt do normy, po prostu usuń wszystkie wiersze za pomocą GOTO.

Możemy również upiększyć to rozwiązanie, dodając gotopolecenie jako alias:

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

Aliasy zwykle nie działają w skryptach bash, więc potrzebujemy shoptpolecenia, aby to naprawić.

Jeśli chcesz mieć możliwość włączania / wyłączania swoich goto, potrzebujemy trochę więcej:

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

Następnie możesz to zrobić export DEBUG=TRUEprzed uruchomieniem skryptu.

Etykiety są komentarzami, więc nie spowodują błędów składniowych, jeśli wyłączymy nasze goto(ustawiając gotoopcję :„no-op”), ale oznacza to, że musimy zacytować je w naszych gotowypowiedziach.

Ilekroć korzystasz z jakiegokolwiek gotorozwiązania, musisz uważać, aby przeskakujący kod nie ustawiał żadnych zmiennych, na których będziesz polegać później - być może trzeba przenieść te definicje na początek skryptu lub nieco powyżej twoich gotooświadczeń.

Laurence Renshaw
źródło
Bez wchodzenia w dobrą / złą naturę goto, rozwiązanie Laurence'a jest po prostu fajne. Masz mój głos.
Mogens TrasherDK
1
To bardziej przypomina przejście do, i if(false)wydaje mi się, że ma większy sens (dla mnie).
vesperto
2
Cytując goto „etykietę” oznacza również, że niniejszy dokument nie jest rozwijany / oceniany, co oszczędza ci trudnych do znalezienia błędów (bez cudzysłowu, podlegałoby to: rozszerzaniu parametrów, podstawianiu poleceń i rozszerzaniu arytmetycznemu, które wszyscy mogą mieć skutki uboczne). Możesz również trochę to ulepszyć alias goto=":<<"i catcałkowicie zrezygnować .
mr. Spuratic
tak, vesperto, możesz użyć wyrażenia „jeśli” i zrobiłem to w wielu skryptach, ale robi się to bardzo bałagan i jest znacznie trudniejszy do kontrolowania i śledzenia, szczególnie jeśli chcesz często zmieniać punkty skoku.
Laurence Renshaw
12

Chociaż inni już wyjaśnili, że nie ma bezpośredniego gotoodpowiednika w bash (i podali najbliższe alternatywy, takie jak funkcje, pętle i przerwanie), chciałbym zilustrować, w jaki sposób użycie plus plus breakmoże symulować określony typ instrukcji goto.

Najbardziej przydatne jest to, że muszę wrócić do początku sekcji kodu, jeśli nie zostaną spełnione określone warunki. W poniższym przykładzie pętla while będzie działać wiecznie, aż ping przestanie upuszczać pakiety do testowego adresu 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
Seth McCauley
źródło
7

W tym rozwiązaniu wystąpiły następujące problemy:

  • Bezkrytycznie usuwa wszystkie wiersze kodu kończące się na :
  • Traktuje w label:dowolnym miejscu linii jako etykietę

Oto poprawiona ( shell-checkczysta) wersja:


#!/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
Tom Hale
źródło
6

Jest jeszcze jedna możliwość osiągnięcia pożądanych rezultatów: polecenie trap. Można go na przykład wykorzystać do czyszczenia.

Serge Roussak
źródło
4

Nie ma gotobashu.

Oto niektóre nieprzyjemne obejście, za pomocą trapktórego przeskakuje tylko do tyłu :)

#!/bin/bash -e
trap '
echo I am
sleep 1
echo here now.
' EXIT

echo foo
goto trap 2> /dev/null
echo bar

Wynik:

$ ./test.sh 
foo
I am
here now.

Nie należy tego wykorzystywać w ten sposób, a jedynie w celach edukacyjnych. Oto dlaczego to działa:

trapużywa obsługi wyjątków, aby osiągnąć zmianę w przepływie kodu. W tym przypadku trapłapie wszystko, co powoduje zakończenie skryptu. Polecenie gotonie istnieje i dlatego generuje błąd, który zwykle kończy skrypt. Ten błąd jest wychwytywany trapi 2>/dev/nullukrywa komunikat o błędzie, który zwykle byłby wyświetlany.

Ta implementacja goto jest oczywiście niewiarygodna, ponieważ każde nieistniejące polecenie (lub jakikolwiek inny błąd, w ten sposób), wykonałoby to samo polecenie pułapki. W szczególności nie można wybrać, do której etykiety należy przejść.


Zasadniczo w prawdziwym scenariuszu nie potrzebujesz żadnych instrukcji goto, są one zbędne, ponieważ przypadkowe połączenia do różnych miejsc utrudniają zrozumienie kodu.

Jeśli Twój kod jest wywoływany wiele razy, rozważ użycie pętli i zmianę przepływu pracy na użycie continuei break.

Jeśli kod sam się powtarza, rozważ napisanie funkcji i wywołanie jej tyle razy, ile chcesz.

Jeśli Twój kod musi przejść do określonej sekcji na podstawie wartości zmiennej, rozważ użycie caseinstrukcji.

Jeśli możesz podzielić swój długi kod na mniejsze części, rozważ przeniesienie go do oddzielnych plików i wywołaj go ze skryptu nadrzędnego.

kenorb
źródło
jakie są różnice między tą formą a normalną funkcją?
yurenchen
2
@yurenchen - Pomyśl o trapużyciu jako narzędzia exception handlingdo osiągnięcia zmiany w przepływie kodu. W tym przypadku pułapka łapie wszystko, co powoduje skrypt EXIT, co jest uruchamiane przez wywołanie nieistniejącej komendy goto. BTW: Argumentem goto trapmoże być cokolwiek, goto ignoredponieważ to on gotopowoduje WYJŚCIE, a 2>/dev/nullukrywa komunikat o błędzie informujący, że skrypt się kończy.
Jesse Chisholm,
2

Jest to niewielka korekta skryptu Judy Schmidt opublikowanego przez Hubbbitus.

Umieszczanie nieskapowanych etykiet w skrypcie było problematyczne na komputerze i powodowało awarię. To było wystarczająco łatwe do rozwiązania, dodając #, aby uniknąć etykiet. Podziękowania dla Alexej Magury i access_granted za ich sugestie.

#!/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
źródło
Ta pasta do kopiowania nie działa => nadal jest jumpto.
Alexis Paques
Co to znaczy? Co nie działa? Możesz być bardziej dokładny?
thebunnyrules
Oczywiście próbowałem kodu! Testowałem w 5 lub 6 różnych warunkach przed opublikowaniem wszystkich udanych. W jaki sposób kod uległ awarii, jaki komunikat o błędzie lub kod?
thebunnyrules,
Jakikolwiek człowiek. Chcę ci pomóc, ale jesteś naprawdę niejasny i nieskomunikowany (nie mówiąc już o obrażeniu tego pytania „przetestowałeś to”). Co oznacza „nie wkleiłeś poprawnie”? Jeśli masz na myśli „/ $ # label #: / {: a; n; p; ba};” po części jestem bardzo świadomy, że jest inaczej. Zmodyfikowałem go dla nowego formatu etykiety (aka # label # vs: label w innej odpowiedzi, która w niektórych przypadkach powodowała awarię skryptu). Czy masz jakiś ważny problem z moją odpowiedzią (np. Awaria skryptu lub zła składnia), czy po prostu -1 moją odpowiedź, ponieważ pomyślałeś: „Nie wiem, jak wycinać i wklejać”?
thebunnyrules
1
@ thebunnyrules Natrafiłem na ten sam problem co ty, ale rozwiązałem go inaczej +1 dla twojego rozwiązania!
Fabby
2

Prosta wyszukiwarka goto do komentowania bloków kodu podczas debugowania.

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

Wynik -> GOTO zrobione

ztalbot
źródło
1

Znalazłem sposób na zrobienie tego za pomocą funkcji.

Załóżmy na przykład, że masz 3 możliwości: A, B, i C. Ai Bwykonać polecenie, ale Cdaje więcej informacji i ponownie prowadzi do pierwotnego monitu. Można to zrobić za pomocą funkcji.

Zauważ, że ponieważ function demoFunctionkonfiguracja linii polega tylko na ustawieniu funkcji, musisz wywołać demoFunctionten skrypt, aby funkcja mogła się uruchomić.

Możesz to łatwo dostosować, pisząc wiele innych funkcji i wywołując je, jeśli potrzebujesz " GOTO" innego miejsca w skrypcie powłoki.

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
cutrightjm
źródło