Dalam skrip Bash, bagaimana saya bisa keluar dari seluruh skrip jika kondisi tertentu terjadi?

717

Saya sedang menulis skrip di Bash untuk menguji beberapa kode. Namun, tampaknya konyol untuk menjalankan tes jika kompilasi kode gagal di tempat pertama, dalam hal ini saya hanya akan membatalkan tes.

Apakah ada cara saya bisa melakukan ini tanpa membungkus seluruh skrip dalam loop sementara dan menggunakan jeda? Sesuatu seperti dun dun dun goto?

samoz
sumber

Jawaban:

810

Coba pernyataan ini:

exit 1

Ganti 1dengan kode kesalahan yang sesuai. Lihat juga Kode Keluar Dengan Makna Khusus .

Michael Foukarakis
sumber
4
@ CMCDragonkai, biasanya kode yang bukan nol akan berfungsi. Jika Anda tidak membutuhkan sesuatu yang istimewa, Anda dapat menggunakannya 1secara konsisten. Jika skrip dimaksudkan untuk dijalankan oleh skrip lain, Anda mungkin ingin mendefinisikan kumpulan kode status Anda sendiri dengan makna tertentu. Misalnya, 1== tes gagal, 2== kompilasi gagal. Jika skrip adalah bagian dari sesuatu yang lain, Anda mungkin perlu menyesuaikan kode untuk mencocokkan praktik yang digunakan di sana. Misalnya, ketika bagian dari rangkaian uji dijalankan oleh automake, kode 77tersebut digunakan untuk menandai tes yang dilewati.
Michał Górny
21
tidak ada yang menutup jendela juga, tidak hanya keluar dari skrip
Toni Leigh
7
@ToniLeigh bash tidak memiliki konsep "jendela", Anda mungkin bingung sehubungan dengan apa yang diatur oleh pengaturan khusus Anda - misalnya emulator terminal -.
Michael Foukarakis
4
@ToniLeigh Jika itu menutup "jendela" kemungkinan Anda meletakkan exit #perintah di dalam suatu fungsi, bukan skrip. (Dalam hal ini digunakan return #sebagai gantinya.)
Jamie
1
@Sevenearths 0 berarti berhasil, lalu exit 0keluar dari skrip dan mengembalikan 0 (memberi tahu skrip lain yang mungkin menggunakan hasil skrip ini berhasil)
VictorGalisson
689

Gunakan set -e

#!/bin/bash

set -e

/bin/command-that-fails
/bin/command-that-fails2

Script akan berakhir setelah baris pertama yang gagal (mengembalikan kode keluar bukan nol). Dalam hal ini, perintah-yang-gagal2 tidak akan berjalan.

Jika Anda memeriksa status pengembalian setiap perintah, skrip Anda akan terlihat seperti ini:

#!/bin/bash

# I'm assuming you're using make

cd /project-dir
make
if [[ $? -ne 0 ]] ; then
    exit 1
fi

cd /project-dir2
make
if [[ $? -ne 0 ]] ; then
    exit 1
fi

Dengan set -e akan terlihat seperti:

#!/bin/bash

set -e

cd /project-dir
make

cd /project-dir2
make

Perintah apa pun yang gagal akan menyebabkan seluruh skrip gagal dan mengembalikan status keluar yang dapat Anda periksa dengan $? . Jika skrip Anda sangat panjang atau Anda sedang membangun banyak hal itu akan menjadi sangat jelek jika Anda menambahkan cek status pengembalian di mana-mana.

Shizzmo
sumber
10
Dengan set -eAnda masih dapat membuat beberapa perintah keluar dengan kesalahan tanpa berhenti script: command 2>&1 || echo $?.
Adobe
6
set -eakan membatalkan skrip jika pipa atau struktur perintah mengembalikan nilai bukan nol. Misalnya foo || barakan gagal hanya jika keduanya foodan barmengembalikan nilai bukan nol. Biasanya skrip bash yang ditulis dengan baik akan berfungsi jika Anda menambahkan set -edi awal dan penambahan berfungsi sebagai cek kewarasan otomatis: batalkan skrip jika ada yang salah.
Mikko Rantalainen
6
Jika Anda menyatukan perintah, Anda juga bisa gagal jika ada yang gagal dengan mengatur set -o pipefailopsi.
Jake Biesinger
18
Sebenarnya kode idiomatik tanpa set -eakan adil make || exit $?.
tripleee
4
Anda juga punya set -u. Lihatlah ke tidak resmi modus ketat pesta : set -euo pipefail.
Pablo A
236

Seorang pria SysOps pernah mengajari saya teknik Three-Fingered Claw:

yell() { echo "$0: $*" >&2; }
die() { yell "$*"; exit 111; }
try() { "$@" || die "cannot $*"; }

Fungsi-fungsi ini adalah * NIX OS dan shell-robust. Letakkan di awal skrip Anda (bash atau tidak), try()pernyataan dan kode Anda menyala

Penjelasan

(berdasarkan komentar domba terbang ).

  • yell: cetak nama skrip dan semua argumen ke stderr:
    • $0 adalah jalan menuju naskah;
    • $* semua argumen.
    • >&2berarti >mengarahkan ulang stdout ke & pipa2 . pipa1 itu stdoutsendiri.
  • diemelakukan hal yang sama seperti yell, tetapi keluar dengan status keluar non-0 , yang berarti "gagal".
  • trymenggunakan ||(boolean OR), yang hanya mengevaluasi sisi kanan jika yang kiri gagal.
    • $@semua argumen lagi, tetapi berbeda .
c.gutierrez
sumber
3
Saya cukup baru untuk unix scripting. Bisakah Anda jelaskan bagaimana fungsi-fungsi di atas dijalankan? Saya melihat beberapa sintaks baru yang tidak saya kenal. Terima kasih.
kaizenCoder
15
yell : $0adalah jalan menuju skrip. $*semua argumen. >&2berarti " >mengarahkan stdout ke &pipa 2". pipa 1 akan menjadi stdout sendiri. jadi berteriak awalan semua argumen dengan nama skrip dan cetak ke stderr die melakukan hal yang sama dengan berteriak , tetapi keluar dengan status keluar non-0, yang berarti “gagal”. coba gunakan boolean atau ||, yang hanya mengevaluasi sisi kanan jika yang kiri tidak gagal. $@semua argumen lagi, tetapi berbeda . harapan yang menjelaskan semuanya
domba terbang
1
Saya memodifikasi ini die() { yell "$1"; exit $2; }agar Anda dapat menyampaikan pesan dan keluar dengan kode die "divide by zero" 115.
Mark Lakata
3
Saya bisa melihat bagaimana saya menggunakan yelldan die. Namun, trytidak terlalu banyak. Bisakah Anda memberikan contoh Anda menggunakannya?
kshenoy
2
Hmm, tapi bagaimana Anda menggunakannya dalam skrip? Saya tidak mengerti apa yang Anda maksud dengan "coba () pernyataan dan kode Anda pada".
TheJavaGuy-Ivan Milosavljević
33

Jika Anda akan memanggil skrip source, Anda dapat menggunakan di return <x>mana <x>status skrip akan keluar (gunakan nilai bukan nol untuk kesalahan atau salah). Tetapi jika Anda menjalankan skrip yang dapat dieksekusi (yaitu, langsung dengan nama filenya), pernyataan kembali akan menghasilkan komplain (pesan kesalahan "kembali: hanya dapat` kembali 'dari suatu fungsi atau skrip bersumber ").

Jika exit <x> digunakan sebagai gantinya, ketika skrip dipanggil dengan source, itu akan menghasilkan keluar shell yang memulai skrip, tetapi skrip yang dapat dieksekusi hanya akan berakhir, seperti yang diharapkan.

Untuk menangani kedua kasus dalam skrip yang sama, Anda dapat menggunakan

return <x> 2> /dev/null || exit <x>

Ini akan menangani permintaan mana saja yang cocok. Itu dengan asumsi Anda akan menggunakan pernyataan ini di tingkat atas skrip. Saya akan menyarankan agar tidak langsung keluar dari skrip dari dalam suatu fungsi.

Catatan: <x>seharusnya hanya angka.

kavadias
sumber
Tidak berfungsi untuk saya dalam skrip dengan pengembalian / keluar di dalam suatu fungsi, yaitu apakah mungkin untuk keluar di dalam fungsi tanpa ada shell, tetapi tanpa membuat fungsi pemanggil memperhatikan pemeriksaan kode pengembalian fungsi yang benar ?
Januari
@jan Apa yang dilakukan penelepon (atau tidak melakukan) dengan nilai kembali, benar-benar ortogonal (yaitu, terlepas dari) bagaimana Anda kembali dari fungsi (... w / o keluar dari shell, tidak peduli apa doa). Ini terutama tergantung pada kode penelepon, yang bukan bagian dari T&J ini. Anda bahkan dapat menyesuaikan nilai kembali fungsi dengan kebutuhan pemanggil, tetapi jawaban ini tidak membatasi berapa nilai pengembalian ini ...
kavadias
11

Saya sering menyertakan fungsi yang disebut run () untuk menangani kesalahan. Setiap panggilan yang ingin saya lakukan diteruskan ke fungsi ini sehingga seluruh skrip keluar saat kegagalan terjadi. Keuntungan dari solusi set -e ini adalah skrip tidak keluar secara diam-diam ketika sebuah baris gagal, dan dapat memberi tahu Anda apa masalahnya. Pada contoh berikut, baris ke-3 tidak dieksekusi karena skrip keluar saat dipanggil ke false.

function run() {
  cmd_output=$(eval $1)
  return_value=$?
  if [ $return_value != 0 ]; then
    echo "Command $1 failed"
    exit -1
  else
    echo "output: $cmd_output"
    echo "Command succeeded."
  fi
  return $return_value
}
run "date"
run "false"
run "date"
Joseph Sheedy
sumber
1
Sobat, untuk beberapa alasan, saya sangat suka jawaban ini. Saya tahu ini sedikit lebih rumit, tetapi sepertinya sangat berguna. Dan mengingat bahwa saya bukan ahli bash, itu membuat saya percaya bahwa logika saya salah, dan ada yang salah dengan metodologi ini, jika tidak, saya merasa orang lain akan memberikan pujian lebih. Jadi, apa masalahnya dengan fungsi ini? Apakah ada sesuatu yang harus saya perhatikan di sini?
Saya tidak ingat alasan saya menggunakan eval, fungsinya berfungsi baik dengan cmd_output = $ ($ 1)
Joseph Sheedy
Saya baru saja mengimplementasikan ini sebagai bagian dari proses penyebaran yang rumit dan ini berhasil dengan fantastis. Terima kasih dan ini komentar dan upvote.
fuzzygroup
Pekerjaan yang benar-benar luar biasa! Ini adalah solusi paling sederhana dan paling bersih yang bekerja dengan baik. Bagi saya, saya menambahkan ini sebelum perintah dalam loop FOR karena loop FOR tidak akan mengambil set -e option. Kemudian perintah, karena dengan argumen, saya menggunakan tanda kutip tunggal untuk menghindari masalah pesta suka begitu: runTry 'mysqldump $DB_PASS --user="$DB_USER" --host="$BV_DB_HOST" --triggers --routines --events --single-transaction --verbose $DB_SCHEMA $tables -r $BACKUP_DIR/$tables$BACKUP_FILE_NAME'. Catatan saya mengubah nama fungsi menjadi runTry.
Tony-Caffe
1
evalberpotensi berbahaya jika Anda menerima input sewenang-wenang, tetapi sebaliknya ini terlihat cukup bagus.
dragon788
5

Alih-alih ifmembangun, Anda dapat meningkatkan evaluasi hubungan pendek :

#!/usr/bin/env bash

echo $[1+1]
echo $[2/0]              # division by 0 but execution of script proceeds
echo $[3+1]
(echo $[4/0]) || exit $? # script halted with code 1 returned from `echo`
echo $[5+1]

Perhatikan pasangan tanda kurung yang diperlukan karena prioritas operator pergantian. $?adalah variabel khusus yang diatur untuk keluar dari kode yang paling terakhir disebut perintah.

skalee
sumber
1
jika saya melakukannya command -that --fails || exit $?tanpa tanda kurung, apa echo $[4/0]yang menyebabkan kita membutuhkannya?
Anentropic
2
@Anentropic @skalee Tanda kurung tidak ada hubungannya dengan prioritas, tetapi penanganan pengecualian. Divide-by-zero akan menyebabkan keluar langsung dari shell dengan kode 1. Tanpa tanda kurung (yaitu, sederhana echo $[4/0] || exit $?) bash tidak akan pernah menjalankan echo, apalagi mematuhi ||.
bobbogo
1

Saya memiliki pertanyaan yang sama tetapi tidak dapat menanyakannya karena itu merupakan duplikat.

Jawaban yang diterima, menggunakan keluar, tidak berfungsi ketika skrip sedikit lebih rumit. Jika Anda menggunakan proses latar belakang untuk memeriksa kondisi, keluar hanya keluar dari proses itu, karena berjalan dalam sub-shell. Untuk membunuh skrip, Anda harus membunuhnya secara eksplisit (setidaknya itulah satu-satunya cara saya tahu).

Berikut ini adalah skrip kecil tentang cara melakukannya:

#!/bin/bash

boom() {
    while true; do sleep 1.2; echo boom; done
}

f() {
    echo Hello
    N=0
    while
        ((N++ <10))
    do
        sleep 1
        echo $N
        #        ((N > 5)) && exit 4 # does not work
        ((N > 5)) && { kill -9 $$; exit 5; } # works 
    done
}

boom &
f &

while true; do sleep 0.5; echo beep; done

Ini adalah jawaban yang lebih baik tetapi masih belum lengkap. Saya benar-benar tidak tahu bagaimana cara menyingkirkan bagian booming .

Gyro Gearloose
sumber