Batalkan skrip shell jika ada perintah yang mengembalikan nilai bukan nol?

437

Saya memiliki skrip Bash shell yang memanggil sejumlah perintah. Saya ingin agar skrip shell secara otomatis keluar dengan nilai kembali 1 jika ada perintah yang mengembalikan nilai bukan nol.

Apakah ini mungkin tanpa secara eksplisit memeriksa hasil dari setiap perintah?

misalnya

dosomething1
if [[ $? -ne 0 ]]; then
    exit 1
fi

dosomething2
if [[ $? -ne 0 ]]; then
    exit 1
fi
Jin Kim
sumber
8
Selain itu set -e, lakukan juga set -u(atau set -eu). -umengakhiri perilaku idiot, penyembunyian bug yang Anda dapat mengakses variabel tidak ada dan memiliki nilai kosong yang dihasilkan tanpa diagnostik.
Kaz

Jawaban:

742

Tambahkan ini ke awal skrip:

set -e

Ini akan menyebabkan shell keluar segera jika perintah sederhana keluar dengan nilai keluar yang bukan nol. Perintah sederhana adalah perintah apa pun yang bukan bagian dari jika, sementara, atau sampai pengujian, atau bagian dari && atau || daftar.

Lihat halaman manual bash (1) pada perintah internal "set" untuk lebih jelasnya.

Saya pribadi memulai hampir semua skrip shell dengan "set -e". Sangat menjengkelkan untuk memiliki skrip yang terus berlanjut ketika sesuatu gagal di tengah dan mematahkan asumsi untuk sisa skrip.

Ville Laurikari
sumber
36
Itu akan berhasil, tapi saya suka menggunakan "#! / Usr / bin / env bash" karena saya sering menjalankan bash dari tempat lain selain / bin. Dan "#! / Usr / bin / env bash -e" tidak berfungsi. Selain itu, senang memiliki tempat untuk memodifikasi untuk membaca "set -xe" ketika saya ingin mengaktifkan pelacakan untuk debugging.
Ville Laurikari
48
Juga, bendera pada baris shebang diabaikan jika skrip dijalankan sebagai bash script.sh.
Tom Anderson
27
Hanya sebuah catatan: Jika Anda mendeklarasikan fungsi di dalam skrip bash, fungsi tersebut harus memiliki set -e dideklarasikan ulang di dalam tubuh fungsi jika Anda ingin memperluas fungsi ini.
Jin Kim
8
Juga, jika Anda sumber skrip Anda, garis shebang akan irrelevent.
4
@JinKim Tampaknya bukan kasus di bash 3.2.48. Coba berikut dalam naskah: set -e; tf() { false; }; tf; echo 'still here'. Bahkan tanpa set -edi dalam tubuh tf(), eksekusi dibatalkan. Mungkin Anda bermaksud mengatakan bahwa set -eitu tidak diwarisi oleh subkulit , yang benar.
mklement0
202

Untuk menambah jawaban yang diterima:

Camkan itu set -e kadang kadang tidak cukup, khususnya jika Anda memiliki pipa.

Misalnya, Anda memiliki skrip ini

#!/bin/bash
set -e 
./configure  > configure.log
make

... yang berfungsi seperti yang diharapkan: kesalahan dalam configure membatalkan eksekusi.

Besok Anda membuat perubahan yang tampaknya sepele:

#!/bin/bash
set -e 
./configure  | tee configure.log
make

... dan sekarang tidak berfungsi. Ini dijelaskan di sini , dan solusi (hanya Bash) disediakan:

#! / bin / bash
set -e 
set -o pipefail

./configure | tee configure.log
membuat
leonbloy
sumber
1
Terima kasih telah menjelaskan pentingnya harus pipefailikut set -o!
Malcolm
83

Pernyataan if dalam contoh Anda tidak perlu. Lakukan saja seperti ini:

dosomething1 || exit 1

Jika Anda mengikuti saran Ville Laurikari dan menggunakannya set -euntuk beberapa perintah, Anda mungkin perlu menggunakan ini:

dosomething || true

The || trueakan membuat pipa perintah memiliki truenilai kembali bahkan jika perintah gagal sehingga para -eopsi tidak akan membunuh script.

Zan Lynx
sumber
1
Saya suka ini. Terutama karena jawaban teratas adalah bash-centric (sama sekali tidak jelas bagi saya apakah / sejauh mana itu berlaku untuk skrip zsh). Dan saya bisa mencarinya, tetapi Anda lebih jelas, karena logika.
g33kz0r
set -ebukan bash-centric - itu didukung bahkan pada Shell Bourne asli.
Marcos Vives Del Sol
27

Jika Anda memiliki pembersihan yang harus Anda lakukan saat keluar, Anda juga dapat menggunakan 'jebakan' dengan ERR sinyal semu. Ini bekerja dengan cara yang sama seperti menjebak INT atau sinyal lainnya; bash melempar ERR jika ada perintah yang keluar dengan nilai bukan nol:

# Create the trap with   
#    trap COMMAND SIGNAME [SIGNAME2 SIGNAME3...]
trap "rm -f /tmp/$MYTMPFILE; exit 1" ERR INT TERM
command1
command2
command3
# Partially turn off the trap.
trap - ERR
# Now a control-C will still cause cleanup, but
# a nonzero exit code won't:
ps aux | grep blahblahblah

Atau, terutama jika Anda menggunakan "set -e", Anda bisa menjebak EXIT; perangkap Anda kemudian akan dieksekusi ketika skrip keluar karena alasan apa pun, termasuk akhir yang normal, interupsi, jalan keluar yang disebabkan oleh opsi -e, dll.

badak
sumber
12

The $?variabel jarang diperlukan. Idi semu command; if [ $? -eq 0 ]; then X; fiharus selalu ditulis sebagai if command; then X; fi.

Kasus-kasus di mana $?diperlukan adalah ketika itu perlu diperiksa terhadap beberapa nilai:

command
case $? in
  (0) X;;
  (1) Y;;
  (2) Z;;
esac

atau ketika $?perlu digunakan kembali atau dimanipulasi:

if command; then
  echo "command successful" >&2
else
  ret=$?
  echo "command failed with exit code $ret" >&2
  exit $ret
fi
Mark Edgar
sumber
3
Mengapa "harus selalu ditulis sebagai"? Maksudku, mengapa " harus " begitu? Ketika sebuah perintah panjang (coba gunakan GCC dengan selusin opsi), maka jauh lebih mudah untuk menjalankan perintah sebelum memeriksa status pengembalian.
ysap
Jika sebuah perintah terlalu panjang, Anda dapat memecahnya dengan memberi nama (mendefinisikan fungsi shell).
Mark Edgar
12

Jalankan dengan -eatau set -edi atas.

Lihat juga set -u.

lumpynose
sumber
34
Untuk berpotensi menyelamatkan orang lain, kebutuhan untuk membaca help set: -umemperlakukan referensi untuk menghapus variabel sebagai kesalahan.
mklement0
1
jadi itu salah satu set -uatau set -etidak keduanya? @lumpynose
ericn
1
@ eric Saya pensiun beberapa tahun yang lalu. Meskipun saya menyukai pekerjaan saya, otak saya yang sudah tua telah melupakan segalanya. Begitu saja saya kira Anda bisa menggunakan keduanya bersama-sama; kata-kata buruk di pihak saya; Saya seharusnya mengatakan "dan / atau".
lumpynose
3

Ekspresi suka

dosomething1 && dosomething2 && dosomething3

akan berhenti memproses ketika salah satu perintah kembali dengan nilai yang tidak nol. Misalnya, perintah berikut tidak akan pernah mencetak "selesai":

cat nosuchfile && echo "done"
echo $?
1
Gabor
sumber
2
#!/bin/bash -e

harus cukup.

Baligh Uddin
sumber
-2

hanya melemparkan satu lagi untuk referensi karena ada pertanyaan tambahan untuk input Mark Edgars dan berikut adalah contoh tambahan dan menyentuh topik secara keseluruhan:

[[ `cmd` ]] && echo success_else_silence

yang sama dengan yang cmd || exit errcodeditunjukkan seseorang.

misalnya. Saya ingin memastikan partisi dilepas jika dipasang:

[[ `mount | grep /dev/sda1` ]] && umount /dev/sda1 
Malina
sumber
5
Tidak, [[ cmd`]] `bukan hal yang sama. Itu salah jika output perintah kosong dan benar sebaliknya, terlepas dari status keluar perintah.
Gilles 'SO- stop being evil'