Apakah ada pernyataan "goto" di bash?

206

Apakah ada pernyataan "goto" di bash? Saya tahu Ini dianggap praktik yang buruk, tetapi saya perlu "goto" secara khusus.

kofucii
sumber
4
Tidak, tidak ada gotodi bash (setidaknya katanya command not founduntuk saya). Mengapa? Kemungkinan ada cara yang lebih baik untuk melakukannya.
Niklas B.
153
Dia mungkin punya alasan. Saya menemukan pertanyaan ini karena saya ingin gotopernyataan untuk melewatkan banyak kode untuk debugging skrip besar tanpa menunggu satu jam untuk menyelesaikan berbagai tugas yang tidak berhubungan. Saya tentu saja tidak menggunakan gotokode produksi, tetapi untuk debug kode saya, itu akan membuat hidup saya jauh lebih mudah, dan akan lebih mudah dikenali ketika datang untuk menghapusnya.
Karl Nicoll
30
@delnan Tapi tidak punya goto bisa membuat beberapa hal lebih rumit. Memang ada use case.
glglgl
71
Saya muak dengan mitos goto ini! Tidak ada yang salah dengan goto! Semua yang Anda tulis akhirnya menjadi kebohongan. Di assembler, hanya ada goto. Alasan yang baik untuk menggunakan goto dalam bahasa pemrograman yang lebih tinggi adalah misalnya melompat keluar dari loop bersarang dengan cara yang bersih dan mudah dibaca.
Alexander Czutro
25
"Hindari goto" adalah aturan yang bagus. Seperti aturan apa pun, itu harus dipelajari dalam tiga fase. Pertama: ikuti aturan sampai itu sifat kedua. Kedua, belajar memahami alasan aturan tersebut. Ketiga, pelajari pengecualian yang baik untuk aturan tersebut, berdasarkan pada pemahaman yang komprehensif tentang cara mengikuti aturan dan alasan aturan tersebut. Hindari melewatkan langkah-langkah di atas, yang akan seperti menggunakan "goto". ;-)
Jeff Learman

Jawaban:

75

Tidak, tidak ada; melihat §3.2.4 "Compound Perintah" di Bash Reference Manual untuk informasi tentang struktur kontrol yang melakukan eksis. Secara khusus, perhatikan penyebutan breakdan continue, yang tidak sefleksibel goto, tetapi lebih fleksibel dalam Bash daripada dalam beberapa bahasa, dan dapat membantu Anda mencapai apa yang Anda inginkan. (Apa pun yang Anda inginkan ...)

ruakh
sumber
10
Bisakah Anda memperluas "lebih fleksibel dalam Bash daripada dalam beberapa bahasa"?
user239558
21
@ user239558: Beberapa bahasa hanya memungkinkan Anda untuk breakatau continuedari loop paling dalam, sedangkan Bash memungkinkan Anda menentukan berapa level loop untuk dilompati. (Dan bahkan bahasa yang memungkinkan Anda untuk breakatau continuedari loop sewenang-wenang, sebagian besar mengharuskan itu untuk diekspresikan secara statis - misalnya, break foo;akan keluar dari loop berlabel foo- sedangkan di Bash itu diekspresikan secara dinamis - misalnya, break "$foo"akan keluar dari $fooloop. )
ruakh
Saya akan merekomendasikan mendefinisikan fungsi dengan fitur-fitur yang diperlukan dan caling mereka dari bagian lain dari kode.
Blmayer
@ruakh Sebenarnya, banyak bahasa memungkinkan break LABEL_NAME;, alih-alih Bash menjijikkan break INTEGER;.
Sapphire_Brick
1
@ Safhire_Brick: Ya, saya menyebutkan itu di komentar yang Anda balas.
ruakh
124

Jika Anda menggunakannya untuk melewatkan bagian dari skrip besar untuk debugging (lihat komentar Karl Nicoll), maka jika false bisa menjadi pilihan yang baik (tidak yakin apakah "false" selalu tersedia, bagi saya itu di / bin / false) :

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

if false; then

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

fi

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

Kesulitan muncul ketika saatnya untuk merobek kode debug Anda. Konstruk "jika salah" cukup mudah dan mudah diingat, tetapi bagaimana Anda menemukan fi yang cocok? Jika editor Anda memungkinkan Anda untuk memblokir indentasi, Anda bisa membuat indentasi blok yang dilompati (maka Anda ingin mengembalikannya setelah selesai). Atau komentar pada baris fi, tetapi itu harus menjadi sesuatu yang Anda akan ingat, yang saya duga akan sangat tergantung pada programmer.

Michael Rusch
sumber
6
Ya falseselalu tersedia. Tetapi jika Anda memiliki blok kode yang tidak ingin Anda jalankan, cukup beri komentar. Atau hapus (dan lihat di sistem kontrol sumber Anda jika Anda perlu memulihkannya nanti).
Keith Thompson
1
Jika blok kode terlalu panjang untuk mengomentari satu baris sekaligus, lihat trik ini. stackoverflow.com/questions/947897/… Namun, ini tidak membantu editor teks cocok dengan awal sampai akhir, jadi, mereka tidak banyak perbaikan.
Camille Goudeseune
4
"if false" seringkali jauh lebih baik daripada mengomentari kode, karena memastikan bahwa kode terlampir tetap menjadi kode hukum. Alasan terbaik untuk berkomentar kode adalah ketika itu benar-benar perlu dihapus, tetapi ada sesuatu yang perlu diingat - jadi itu hanya komentar, dan tidak lagi "kode".
Jeff Learman
1
Saya menggunakan komentar '#jika salah;'. Dengan begitu saya bisa mencari itu dan menemukan bagian awal dan akhir dari bagian penghapusan debug.
Jon
Jawaban ini seharusnya tidak dibatalkan karena tidak menanggapi pertanyaan OP dengan cara apa pun.
Viktor Joras
54

Ini memang mungkin berguna untuk beberapa kebutuhan debug atau demonstrasi.

Saya menemukan bahwa solusi Bob Copeland http://bobcopeland.com/blog/2012/10/goto-in-bash/ elegan:

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

menghasilkan:

$ ./test.sh
x is 100
$ ./test.sh foo
x is 10
$ ./test.sh mid
This is not printed!
x is 101
Hubbitus
sumber
36
"Pencarian saya untuk membuat bash terlihat seperti bahasa majelis semakin dekat ke selesai." - Wow. Cuma wow.
Brian Agnew
5
satu-satunya hal yang saya ubah adalah membuatnya sehingga label mulai seperti sehingga : start:mereka tidak kesalahan sintaks.
Alexej Magura
5
Akan lebih baik jika Anda melakukan itu: cmd = $ (sed -n "/ # $ label: / {: a; n; p; ba};" $ 0 | grep -v ': $') dengan label yang dimulai dengan: # mulai: => ini akan mencegah kesalahan skrip
access_granted
2
Hm, memang itu kemungkinan besar kesalahan saya. Saya sudah mengedit posting. Terima kasih.
Hubbitus
2
set -xmembantu memahami apa yang terjadi
John Lin
30

Anda dapat menggunakan casebash untuk mensimulasikan goto:

#!/bin/bash

case bar in
  foo)
    echo foo
    ;&

  bar)
    echo bar
    ;&

  *)
    echo star
    ;;
esac

menghasilkan:

bar
star
Paul Brannan
sumber
4
Perhatikan bahwa ini memerlukan bash v4.0+. Namun, ini bukan tujuan umum gototetapi pilihan untuk menerobos casepernyataan tersebut.
mklement0
3
Saya pikir ini harus menjadi jawabannya. Saya memiliki kebutuhan yang tulus untuk pergi ke dalam rangka mendukung kelanjutan pelaksanaan skrip, dari instruksi yang diberikan. ini, dalam segala hal, tetapi semantik, goto, dan semantik dan gula sintaksis lucu, tetapi tidak sepenuhnya diperlukan. solusi hebat, IMO.
nathan g
1
@nathang, apakah itu yang jawabannya tergantung pada apakah kasus Anda kebetulan mesh dengan subset dari kasus umum OP bertanya tentang. Sayangnya, pertanyaannya tentang kasus umum, membuat jawaban ini terlalu sempit untuk menjadi benar. (Apakah pertanyaan itu harus ditutup terlalu luas karena alasan itu adalah diskusi yang berbeda).
Charles Duffy
1
goto lebih dari sekadar memilih. fitur goto berarti dapat melompat ke tempat-tempat berdasarkan beberapa kondisi, bahkan membuat loop seperti aliran ...
Sergio Abreu
19

Jika Anda menguji / men-debug skrip bash, dan hanya ingin melompati maju melewati satu atau lebih bagian kode, berikut ini adalah cara yang sangat sederhana untuk melakukannya yang juga sangat mudah ditemukan dan dihapus nanti (tidak seperti kebanyakan metode lainnya) dijelaskan di atas).

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

Untuk mengembalikan script Anda ke normal, cukup hapus baris apa saja dengan GOTO.

Kami juga dapat melakukan prettify solusi ini, dengan menambahkan gotoperintah sebagai 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"

Alias ​​biasanya tidak bekerja dalam skrip bash, jadi kita perlu shoptperintah untuk memperbaikinya.

Jika Anda ingin dapat mengaktifkan / menonaktifkan Anda goto, kami perlu sedikit lebih banyak:

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

Kemudian bisa Anda lakukan export DEBUG=TRUEsebelum menjalankan skrip.

Label adalah komentar, jadi tidak akan menyebabkan kesalahan sintaks jika menonaktifkan milik kita goto(dengan mengatur gotoke :no-op), tetapi ini berarti kita perlu mengutipnya digoto pernyataan .

Setiap kali menggunakan gotosolusi apa pun , Anda harus berhati-hati bahwa kode yang Anda lewati tidak menetapkan variabel apa pun yang Anda andalkan nanti - Anda mungkin perlu memindahkan definisi tersebut ke bagian atas skrip Anda, atau tepat di atas yang lain gotopernyataan Anda .

Laurence Renshaw
sumber
Tanpa masuk ke kebaikan / keburukan goto, solusi Laurence itu keren. Anda mendapat suara saya.
Mogens TrasherDK
1
Itu lebih seperti sebuah loncatan, yang if(false)tampaknya lebih masuk akal (bagi saya).
vesperto
2
Mengutip "label" goto juga berarti bahwa di sini-doc tidak diperluas / dievaluasi yang menyelamatkan Anda dari beberapa kesulitan untuk menemukan bug (tidak dikutip, itu akan tunduk pada: ekspansi parameter, penggantian perintah, dan ekspansi aritmatika, yang semuanya dapat memiliki efek samping). Anda juga dapat mengubah sedikit alias goto=":<<"dan membuangnyacat semuanya.
mr.spuratic
ya, vesperto, Anda dapat menggunakan pernyataan 'jika', dan saya telah melakukannya dalam banyak skrip, tetapi itu menjadi sangat berantakan dan jauh lebih sulit untuk dikendalikan dan dilacak, terutama jika Anda ingin sering mengubah titik lompatan Anda.
Laurence Renshaw
12

Meskipun yang lain sudah mengklarifikasi bahwa tidak ada gotopadanan langsung dalam bash (dan memberikan alternatif terdekat seperti fungsi, loop, dan break), saya ingin menggambarkan bagaimana menggunakan loop plus breakdapat mensimulasikan jenis pernyataan goto tertentu.

Situasi di mana saya menemukan ini yang paling berguna adalah ketika saya harus kembali ke awal bagian kode jika kondisi tertentu tidak terpenuhi. Dalam contoh di bawah ini, loop sementara akan berjalan selamanya sampai ping berhenti menjatuhkan paket ke IP tes.

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

Solusi ini memiliki masalah berikut:

  • Secara sembarangan menghapus semua baris kode yang diakhiri dengan a :
  • Treates label: mana saja pada garis sebagai label

Ini versi tetap ( shell-checkbersih):


#!/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
sumber
6

Ada satu lagi kemampuan untuk mencapai hasil yang diinginkan: perintah trap. Ini dapat digunakan untuk membersihkan tujuan misalnya.

Serge Roussak
sumber
4

Tidak ada gotodi bash.

Berikut adalah beberapa solusi kotor menggunakan trapyang melompat hanya mundur :)

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

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

Keluaran:

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

Ini tidak boleh digunakan dengan cara itu, tetapi hanya untuk tujuan pendidikan. Inilah mengapa ini bekerja:

trapmenggunakan penanganan pengecualian untuk mencapai perubahan dalam aliran kode. Dalam hal trapini menangkap apa pun yang menyebabkan skrip EXIT. Perintah gototidak ada, dan karenanya mengeluarkan kesalahan, yang biasanya akan keluar dari skrip. Kesalahan ini ditangkap trap, dan2>/dev/null menyembunyikan pesan kesalahan yang biasanya ditampilkan.

Implementasi goto ini jelas tidak dapat diandalkan, karena perintah yang tidak ada (atau kesalahan lainnya, dengan cara itu), akan menjalankan perintah perangkap yang sama. Khususnya, Anda tidak dapat memilih label mana yang akan dikunjungi.


Pada dasarnya dalam skenario nyata Anda tidak memerlukan pernyataan goto, itu berlebihan karena panggilan acak ke tempat yang berbeda hanya membuat kode Anda sulit dimengerti.

Jika kode Anda dipanggil berkali-kali, maka pertimbangkan untuk menggunakan loop dan mengubah alur kerjanya untuk menggunakan continuedan break.

Jika kode Anda berulang, pertimbangkan untuk menulis fungsi dan memanggilnya sebanyak yang Anda inginkan.

Jika kode Anda perlu melompat ke bagian tertentu berdasarkan nilai variabel, maka pertimbangkan untuk menggunakan casepernyataan.

Jika Anda dapat memisahkan kode panjang Anda menjadi potongan-potongan kecil, pertimbangkan untuk memindahkannya ke file terpisah dan memanggilnya dari skrip induk.

kenorb
sumber
apa perbedaan antara formulir ini dan fungsi normal?
yurenchen
2
@Yurenchen - Pikirkan trapsebagai gunakan exception handlinguntuk mencapai perubahan dalam aliran kode. Dalam hal ini jebakan menangkap apa pun yang menyebabkan skrip EXIT, yang dipicu oleh menjalankan perintah yang tidak ada goto. BTW: Argumennya goto trapbisa apa saja, goto ignoredkarena itu gotoyang menyebabkan EXIT, dan 2>/dev/nullmenyembunyikan pesan kesalahan yang mengatakan skrip Anda keluar.
Jesse Chisholm
2

Ini adalah koreksi kecil dari naskah Judy Schmidt yang diajukan oleh Hubbbitus.

Menempatkan label yang tidak diloloskan dalam skrip bermasalah pada mesin dan menyebabkannya macet. Ini cukup mudah untuk diselesaikan dengan menambahkan # untuk keluar dari label. Terima kasih kepada Alexej Magura dan access_granted atas saran mereka.

#!/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..."
aturan kebun
sumber
Salin tempel ini tidak berfungsi => masih ada lompatan.
Alexis Paques
Apa artinya? Apa yang tidak berfungsi? Bisakah kamu lebih spesifik?
thebunnyrules
Tentu saja saya mencoba kodenya! Saya menguji di bawah 5 atau 6 kondisi yang berbeda sebelum memposting semua berhasil. Bagaimana kode gagal untuk Anda, pesan atau kode kesalahan apa?
thebunnyrules
Pria apa pun. Saya ingin membantu Anda tetapi Anda benar-benar kabur dan tidak berkomunikasi (belum lagi menghina dengan pertanyaan "apakah Anda mengujinya"). Apa artinya "Anda tidak menempel dengan benar"? Jika Anda merujuk pada label "/ $ # #: / {: a; n; p; ba};" sebagian, saya sangat sadar bahwa itu berbeda. Saya memodifikasinya untuk format label baru (alias # label # vs: label di jawaban lain yang mengalami crash script dalam beberapa kasus). Apakah Anda memiliki masalah yang valid dengan jawaban saya (seperti script crash atau sintaksis buruk) atau apakah Anda hanya -1 jawaban saya karena Anda berpikir "Saya tidak tahu cara memotong dan menempel"?
thebunnyrules
1
@thebunnyrules Saya mengalami masalah yang sama seperti Anda tetapi menyelesaikannya secara berbeda +1 untuk solusi Anda!
Fabby
2

Sebuah goto yang mudah dicari untuk penggunaan mengomentari blok kode saat debugging.

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

Hasilnya adalah-> GOTO selesai

ztalbot
sumber
1

Saya menemukan cara untuk melakukan ini menggunakan fungsi.

Katakanlah, misalnya, Anda memiliki 3 pilihan: A, B, dan C. Adan Bjalankan perintah, tetapi Cmemberi Anda lebih banyak info dan membawa Anda ke prompt asli lagi. Ini bisa dilakukan menggunakan fungsi.

Perhatikan bahwa karena baris containg function demoFunctionhanya mengatur fungsi, Anda perlu memanggil demoFunctionskrip tersebut sehingga fungsi tersebut benar-benar akan berjalan.

Anda dapat dengan mudah mengadaptasi ini dengan menulis beberapa fungsi lain dan memanggilnya jika Anda perlu " GOTO" tempat lain di skrip shell Anda.

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
sumber