Bagaimana cara keluar jika suatu perintah gagal?

222

Saya seorang noob di shell-scripting. Saya ingin mencetak pesan dan keluar dari skrip saya jika ada perintah yang gagal. Saya sudah mencoba:

my_command && (echo 'my_command failed; exit)

tetapi tidak berhasil. Itu terus menjalankan instruksi mengikuti baris ini dalam skrip. Saya menggunakan Ubuntu dan bash.

pengguna459246
sumber
5
Apakah Anda bermaksud kutipan tidak tertutup itu menjadi kesalahan sintaksis yang akan menyebabkan kegagalan / keluar? jika tidak, Anda harus menutup kutipan dalam contoh Anda.
Hobs

Jawaban:

406

Mencoba:

my_command || { echo 'my_command failed' ; exit 1; }

Empat perubahan:

  • Ubah &&ke||
  • Gunakan { }di tempat( )
  • Perkenalkan ;setelah exitdan
  • spasi setelah {dan sebelumnya}

Karena Anda ingin mencetak pesan dan keluar hanya ketika perintah gagal (keluar dengan nilai bukan nol), Anda memerlukan ||bukan &&.

cmd1 && cmd2

akan berjalan cmd2ketika cmd1berhasil (nilai keluar 0). Dimana sebagai

cmd1 || cmd2

akan berjalan cmd2ketika cmd1gagal (nilai keluar bukan nol).

Menggunakan ( )membuat perintah di dalamnya berjalan di sub-shell dan memanggil exitdari sana menyebabkan Anda keluar dari sub-shell dan bukan shell asli Anda, maka eksekusi berlanjut di shell asli Anda.

Untuk mengatasi ini gunakan { }

Dua perubahan terakhir diperlukan oleh bash.

codaddict
sumber
4
Tampaknya "terbalik". Jika fungsi "berhasil" mengembalikan 0 dan jika "gagal" mengembalikan non-nol karena itu && mungkin diharapkan untuk mengevaluasi ketika babak pertama kembali bukan nol. Itu tidak berarti jawaban di atas salah - tidak benar. && dan || dalam skrip bekerja berdasarkan kesuksesan bukan pada nilai balik.
CashCow
7
Tampaknya terbalik, tetapi bacalah dan masuk akal: "lakukan perintah ini (berhasil)" ATAU "cetak kesalahan ini dan keluar"
simpleuser
2
Logika di baliknya adalah bahwa bahasa tersebut menggunakan evaluasi hubung singkat (SCE). Dengan SCE, f ekspresi dalam bentuk "p OR q", dan p dievaluasi benar, maka tidak ada alasan untuk melihat q. Jika ekspresi dalam bentuk "p DAN q", dan p dievaluasi salah, tidak ada alasan untuk melihat q. Alasan untuk ini adalah dua kali lipat: 1) lebih cepat, untuk alasan yang jelas dan 2) itu menghindari jenis kesalahan tertentu (misalnya: "jika x! = 0 DAN 10 / x> 5" akan macet jika tidak ada SCE ). Kemampuan untuk menggunakannya di baris perintah seperti ini adalah efek samping yang menyenangkan.
user2635263
2
Saya akan merekomendasikan untuk menggemakan STDERR:{ (>&2 echo 'my_command failed') ; exit 1; }
rynop
127

Jawaban lain telah mencakup pertanyaan langsung dengan baik, tetapi Anda mungkin juga tertarik untuk menggunakannya set -e. Dengan itu, perintah apa pun yang gagal (di luar konteks tertentu seperti iftes) akan menyebabkan skrip dibatalkan. Untuk skrip tertentu, ini sangat berguna.

Daenyth
sumber
3
Sayangnya, itu juga sangat berbahaya - set -ememiliki seperangkat aturan yang panjang dan misterius tentang kapan ia bekerja dan kapan tidak. (Jika ada yang menjalankan if yourfunction; then ..., maka set -etidak akan pernah menembak ke dalam yourfunctionatau apa pun yang dipanggil, karena kode itu dianggap "dicentang"). Lihat bagian latihan BashFAQ # 105 , membahas ini dan jebakan lainnya (beberapa di antaranya hanya berlaku untuk versi shell tertentu, jadi Anda harus memastikan untuk menguji setiap rilis yang mungkin digunakan untuk menjalankan skrip Anda).
Charles Duffy
67

Perhatikan juga, status keluar setiap perintah disimpan dalam variabel shell $ ?, yang dapat Anda periksa segera setelah menjalankan perintah. Status bukan nol menunjukkan kegagalan:

my_command
if [ $? -eq 0 ]
then
    echo "it worked"
else
    echo "it failed"
fi
Alex Howansky
sumber
10
Ini hanya bisa diganti dengan jika my_command, tidak perlu menggunakan perintah tes di sini.
Bart Sas
5
+1 karena saya pikir Anda tidak boleh dihukum karena mendaftar alternatif ini - itu harus di atas meja - meskipun agak jelek dan dalam pengalaman saya terlalu mudah untuk menjalankan perintah lain di antara tanpa memperhatikan sifat tes (mungkin saya ' aku hanya bodoh).
Tony Delroy
1
@ BartSas Jika perintahnya panjang, lebih baik letakkan di barisnya sendiri. Itu membuat skrip lebih mudah dibaca.
Michael
@Michael, tidak jika itu menciptakan dependensi lintas baris, di mana Anda perlu tahu apa yang terjadi pada baris A sebelum Anda dapat memahami baris B (atau, lebih buruk lagi, perlu tahu apa yang akan terjadi pada baris B sebelum Anda tahu bahwa aman untuk menambahkan sesuatu baru setelah akhir baris A). Saya telah melihat terlalu banyak seseorang menambahkan echo "Just finished step A", tidak tahu bahwa itu echomenimpa $?yang akan diperiksa nanti.
Charles Duffy
67

Jika Anda menginginkan perilaku itu untuk semua perintah dalam skrip Anda, tambahkan saja

  set -e 
  set -o pipefail

di awal skrip. Sepasang opsi ini memberi tahu juru bahasa untuk keluar kapan pun perintah kembali dengan kode keluar yang tidak nol.

Ini tidak memungkinkan Anda untuk mencetak pesan keluar.

damienfrancois
sumber
8
Anda dapat menjalankan perintah saat keluar menggunakan perintah trapbash built-in.
Gavin Smith
Ini adalah jawaban untuk pertanyaan XY.
jwg
5
Mungkin menarik untuk menjelaskan perintah apa yang dilakukan secara independen daripada pasangan. Terutama karena set -o pipefailmungkin bukan perilaku yang diinginkan. Masih terima kasih telah menunjukkannya!
Antoine Pinsard
3
set -esama sekali tidak dapat diandalkan, konsisten atau dapat diprediksi! Untuk meyakinkan diri sendiri tentang hal itu, tinjau bagian latihan BashFAQ # 105 , atau tabel yang membandingkan perilaku kerang yang berbeda di in-ulm.de/~mascheck/various/set-e
Charles Duffy
14

Saya telah meretas idiom berikut:

echo "Generating from IDL..."
idlj -fclient -td java/src echo.idl
if [ $? -ne 0 ]; then { echo "Failed, aborting." ; exit 1; } fi

echo "Compiling classes..."
javac *java
if [ $? -ne 0 ]; then { echo "Failed, aborting." ; exit 1; } fi

echo "Done."

Awali setiap perintah dengan gema informatif, dan ikuti setiap perintah dengan
if [ $? -ne 0 ];...baris yang sama . (Tentu saja, Anda dapat mengedit pesan kesalahan itu jika Anda mau.)

Berikan Birchmeier
sumber
Ini dapat didefinisikan sebagai fungsi, sehingga menggunakannya kembali.
Eric Wang
1
'jika [$? -ne 0]; lalu ... fi 'adalah cara yang rumit untuk menulis' || '
kevin cline
if ! javac *.java; then ...- Kenapa repot-repot $?sama sekali?
Charles Duffy
Charles - karena saya adalah bash scripter terbaik-biasa saja :)
Grant Birchmeier
10

Asalkan my_commanddirancang secara kanonik, yaitu mengembalikan 0 ketika berhasil, maka &&kebalikan dari yang Anda inginkan. Kamu ingin ||.

Perhatikan juga bahwa (tampaknya tidak benar bagi saya di bash, tetapi saya tidak dapat mencoba dari tempat saya berada. Katakan padaku.

my_command || {
    echo 'my_command failed' ;
    exit 1; 
}
Benoit
sumber
IIRC, Anda perlu titik koma setelah setiap baris di dalam {}
Alex Howansky
9
@ Alex: tidak jika mereka berada di jalur terpisah.
Dijeda sampai pemberitahuan lebih lanjut.
5

Anda juga dapat menggunakan, jika Anda ingin mempertahankan status kesalahan keluar, dan memiliki file yang dapat dibaca dengan satu perintah per baris:

my_command1 || exit $?
my_command2 || exit $?

Namun, ini tidak akan mencetak pesan kesalahan tambahan. Namun dalam beberapa kasus, kesalahan akan dicetak oleh perintah yang gagal.

aleksirin
sumber
1
Tidak ada alasan untuk itu exit $?; hanya exitmenggunakan $?sebagai nilai standarnya.
Charles Duffy
@CharlesDuffy tidak tahu. Luar biasa, jadi bahkan lebih sederhana!
alexpirine
3

The trapshell builtin memungkinkan sinyal penangkapan, dan kondisi lain yang berguna, termasuk gagal eksekusi perintah (yaitu, status tidak nol). Jadi, jika Anda tidak ingin menguji status pengembalian setiap perintah secara eksplisit, Anda dapat mengatakan trap "your shell code" ERRdan kode shell akan dieksekusi kapan saja perintah mengembalikan status tidak nol. Sebagai contoh:

trap "echo script failed; exit 1" ERR

Perhatikan bahwa seperti halnya dengan kasus-kasus lain menangkap perintah yang gagal, pipa membutuhkan perawatan khusus; di atas tidak akan menangkap false | true.

kozel
sumber