Apa yang dimaksud dengan set -e dalam skrip bash?

713

Saya sedang mempelajari konten dari file pertama ini yang dieksekusi oleh skrip sebelum paket tersebut dibuka dari file arsip Debian (.deb).

Script memiliki kode berikut:

#!/bin/bash
set -e
# Automatically added by dh_installinit
if [ "$1" = install ]; then
   if [ -d /usr/share/MyApplicationName ]; then
     echo "MyApplicationName is just installed"
     return 1
   fi
   rm -Rf $HOME/.config/nautilus-actions/nautilus-actions.conf
   rm -Rf $HOME/.local/share/file-manager/actions/*
fi
# End automatically added section

Permintaan pertama saya adalah tentang baris:

set -e

Saya pikir sisa skripnya cukup sederhana: Memeriksa apakah manajer paket Debian / Ubuntu menjalankan operasi pemasangan. Jika ya, ia memeriksa apakah aplikasi saya baru saja diinstal pada sistem. Jika sudah, skrip mencetak pesan "MyApplicationName baru saja diinstal" dan berakhir ( return 1artinya diakhiri dengan "kesalahan", bukan?).

Jika pengguna meminta sistem paket Debian / Ubuntu untuk menginstal paket saya, skrip juga menghapus dua direktori.

Apakah ini benar atau saya kehilangan sesuatu?

AndreaNobili
sumber
42
set -e
Anders Lindahl
46
alasan mengapa Anda tidak dapat menemukan ini di google: -e dalam permintaan Anda ditafsirkan sebagai negasi. Coba pertanyaan berikut: bash set "-e"
Maleev
3
@twalberg Ketika saya bertanya pada diri sendiri pertanyaan yang sama, saya melihatman set
Sedat Kilinc
4
jika Anda mencari cara untuk mematikannya, swap dasbor ke awalan plus:set +e
Tom Saleeba
@twalberg tetapi bertanya pada orang sungguhan jauh lebih menarik daripada hanya membuat permintaan dari robot ;-).
vdegenne

Jawaban:

797

Dari help set:

  -e  Exit immediately if a command exits with a non-zero status.

Tapi itu dianggap praktik buruk oleh beberapa (bash FAQ dan irc freenode #bash FAQ penulis). Disarankan untuk menggunakan:

trap 'do_something' ERR

untuk menjalankan do_somethingfungsi ketika terjadi kesalahan.

Lihat http://mywiki.wooledge.org/BashFAQ/105

Gilles Quenot
sumber
14
Apa yang akan dilakukan do_something jika saya ingin semantik yang sama dengan "Keluar segera jika perintah keluar dengan status bukan nol"?
CMCDragonkai
71
trap 'exit' ERR
chepner
12
The ERRperangkap tidak diwariskan oleh fungsi shell, jadi jika Anda memiliki fungsi, set -o errtraceatau set -Eakan memungkinkan Anda untuk hanya mengatur perangkap sekali dan menerapkannya secara global.
ykay
31
tidak trap 'exit' ERRmelakukan apa-apa yang berbeda dari set -e?
Andy
22
jika itu praktik yang buruk lalu mengapa itu digunakan dalam paket Debian ?
phuclv
98

set -emenghentikan eksekusi skrip jika perintah atau pipa memiliki kesalahan - yang merupakan kebalikan dari perilaku shell default, yaitu mengabaikan kesalahan dalam skrip. Ketik help setterminal untuk melihat dokumentasi untuk perintah bawaan ini.

Robin Green
sumber
45
Ini hanya menghentikan eksekusi jika perintah terakhir dalam sebuah pipa memiliki kesalahan. Ada opsi spesifik Bash, set -o pipefailyang dapat digunakan untuk menyebarkan kesalahan sehingga nilai kembali dari perintah pipa tidak nol jika salah satu perintah sebelumnya keluar dengan status tidak nol.
Anthony Geoghegan
2
Perlu diingat bahwa itu -o pipefailhanya berarti bahwa status keluar dari perintah pertama yang bukan nol (mis. Kesalahan dalam -o errexithal) dari pipa disebarkan ke ujung. Perintah yang tersisa dalam pipa masih berjalan , bahkan dengan set -o errexit. Sebagai contoh: echo success | cat - <(echo piping); echo continues, di mana echo successmerupakan, tetapi perintah keliru sukses, akan mencetak success, pipingdan continues, tetapi false | cat - <(echo piping); echo continues, dengan falsemewakili perintah sekarang erroring diam-diam, masih akan mencetak pipingsebelum keluar.
bb010g
55

Per bash - Manual Set Builtin , jika -e/ errexitdiatur, shell segera keluar jika pipa yang terdiri dari satu perintah sederhana , daftar atau perintah gabungan mengembalikan status tidak nol.

Secara default, status keluar dari pipa adalah status keluar dari perintah terakhir dalam pipa, kecuali jika pipefailopsi ini diaktifkan (dinonaktifkan secara default).

Jika demikian, status pengembalian pipa dari perintah terakhir (paling kanan) untuk keluar dengan status bukan nol, atau nol jika semua perintah berhasil keluar.

Jika Anda ingin mengeksekusi sesuatu saat keluar, coba tentukan trap, misalnya:

trap onexit EXIT

di mana onexitfungsi Anda untuk melakukan sesuatu saat keluar, seperti di bawah ini yang mencetak jejak tumpukan sederhana :

onexit(){ while caller $((n++)); do :; done; }

Ada opsi serupa -E/errtrace yang akan menjebak ERR sebagai gantinya, misalnya:

trap onerr ERR

Contohnya

Contoh status nol:

$ true; echo $?
0

Contoh status bukan nol:

$ false; echo $?
1

Contoh status yang dinegasikan:

$ ! false; echo $?
0
$ false || true; echo $?
0

Tes dengan pipefaildinonaktifkan:

$ bash -c 'set +o pipefail -e; true | true | true; echo success'; echo $?
success
0
$ bash -c 'set +o pipefail -e; false | false | true; echo success'; echo $?
success
0
$ bash -c 'set +o pipefail -e; true | true | false; echo success'; echo $?
1

Tes dengan pipefaildiaktifkan:

$ bash -c 'set -o pipefail -e; true | false | true; echo success'; echo $?
1
kenorb
sumber
55

Saya menemukan pos ini ketika mencoba mencari tahu apa status keluar untuk skrip yang dibatalkan karena a set -e. Jawabannya tidak tampak jelas bagi saya; karenanya jawaban ini. Pada dasarnya, set -ebatalkan eksekusi perintah (mis. Skrip shell) dan kembalikan kode status keluar dari perintah yang gagal (yaitu skrip dalam, bukan skrip luar) .

Misalnya, saya punya skrip shell outer-test.sh:

#!/bin/sh
set -e
./inner-test.sh
exit 62;

Kode untuk inner-test.shadalah:

#!/bin/sh
exit 26;

Ketika saya menjalankan outer-script.shdari baris perintah, skrip luar saya berakhir dengan kode keluar dari skrip dalam:

$ ./outer-test.sh
$ echo $?
26
entpnerd
sumber
10

Saya percaya maksudnya agar skrip yang dimaksud gagal cepat.

Untuk mengujinya sendiri, cukup ketik set -ebash prompt. Sekarang, coba jalankan ls. Anda akan mendapatkan daftar direktori. Sekarang, ketik lsd. Perintah itu tidak dikenali dan akan mengembalikan kode kesalahan, sehingga bash prompt Anda akan ditutup (karena set -e).

Sekarang, untuk memahami ini dalam konteks 'skrip', gunakan skrip sederhana ini:

#!/bin/bash 
# set -e

lsd 

ls

Jika Anda menjalankannya apa adanya, Anda akan mendapatkan daftar direktori dari lspada baris terakhir. Jika Anda menghapus tanda komentar pada set -edan menjalankan lagi, Anda tidak akan melihat daftar direktori sebagai bash berhenti diproses setelah menemui kesalahan dari lsd.

Kallin Nagelberg
sumber
Apakah jawaban ini menambah wawasan atau informasi yang belum diberikan pada orang lain pada pertanyaan?
Charles Duffy
6
Saya pikir ini menawarkan penjelasan yang jelas dan ringkas tentang fungsi yang tidak ada dalam jawaban lain. Tidak ada tambahan, hanya lebih fokus daripada respons lainnya.
Kallin Nagelberg
9

Ini adalah pertanyaan lama, tetapi tidak ada jawaban di sini yang membahas penggunaan set -ealias set -o errexitdalam skrip penanganan paket Debian. Penggunaan opsi ini wajib dalam skrip ini, sesuai kebijakan Debian; maksudnya adalah untuk menghindari kemungkinan kondisi kesalahan yang tidak tertangani.

Apa artinya ini dalam praktek adalah bahwa Anda harus memahami dalam kondisi apa perintah yang Anda jalankan dapat mengembalikan kesalahan, dan menangani masing-masing kesalahan secara eksplisit.

Gotcha umum adalah eg diff(mengembalikan kesalahan ketika ada perbedaan) dan grep(mengembalikan kesalahan ketika tidak ada kecocokan). Anda dapat menghindari kesalahan dengan penanganan eksplisit:

diff this that ||
  echo "$0: there was a difference" >&2
grep cat food ||
  echo "$0: no cat in the food" >&2

(Perhatikan juga bagaimana kami berhati-hati untuk memasukkan nama skrip saat ini dalam pesan, dan menulis pesan diagnostik ke kesalahan standar alih-alih output standar.)

Jika tidak ada penanganan eksplisit yang benar-benar diperlukan atau berguna, secara eksplisit tidak melakukan apa pun:

diff this that || true
grep cat food || :

(Penggunaan :perintah no-op shell sedikit tidak jelas, tetapi cukup umum terlihat.)

Hanya untuk menegaskan kembali,

something || other

adalah singkatan

if something; then
    : nothing
else
    other
fi

yaitu kami secara eksplisit mengatakan otherharus dijalankan jika dan hanya jika somethinggagal. Tangan lama if(dan pernyataan kontrol aliran shell lainnya seperti while, until) juga merupakan cara yang valid untuk menangani kesalahan (memang, jika tidak, skrip shell dengan set -etidak pernah dapat berisi pernyataan kontrol aliran!)

Dan juga, hanya untuk menjadi eksplisit, dengan tidak adanya penangan seperti ini, set -eakan menyebabkan seluruh skrip segera gagal dengan kesalahan jika diffmenemukan perbedaan, atau jika greptidak menemukan kecocokan.

Di sisi lain, beberapa perintah tidak menghasilkan status keluar kesalahan saat Anda menginginkannya. Perintah yang biasanya bermasalah adalah find(status keluar tidak mencerminkan apakah file benar-benar ditemukan) dan sed(status keluar tidak akan mengungkapkan apakah skrip menerima input atau benar-benar menjalankan perintah dengan sukses). Penjaga sederhana dalam beberapa skenario adalah menyalurkan ke perintah yang tidak menjerit jika tidak ada output:

find things | grep .
sed -e 's/o/me/' stuff | grep ^

Perlu dicatat bahwa status keluar dari pipa adalah status keluar dari perintah terakhir dalam pipa itu. Jadi perintah di atas benar-benar sepenuhnya menutupi status finddan sed, dan hanya memberi tahu Anda apakah grepakhirnya berhasil.

(Bash, tentu saja, memiliki set -o pipefail; tetapi skrip paket Debian tidak dapat menggunakan fitur Bash. Kebijakan dengan tegas menentukan penggunaan POSIX shuntuk skrip ini, meskipun hal ini tidak selalu terjadi.)

Dalam banyak situasi, ini adalah sesuatu yang harus diperhatikan secara terpisah ketika melakukan pengkodean secara defensif. Kadang-kadang Anda harus misalnya pergi melalui file sementara sehingga Anda dapat melihat apakah perintah yang menghasilkan output selesai dengan sukses, bahkan ketika idiom dan kenyamanan sebaliknya mengarahkan Anda untuk menggunakan pipa shell.

tripleee
sumber
ini jawaban yang sangat bagus. dan mempromosikan praktik terbaik. Saya memiliki masalah yang sama persis dari perintah GREP dan saya benar-benar tidak ingin menghapus 'set -e'
Minnie
7
Script 1: without setting -e
#!/bin/bash
decho "hi"
echo "hello"
This will throw error in decho and program continuous to next line

Script 2: With setting -e
#!/bin/bash
set -e
decho "hi" 
echo "hello"
# Up to decho "hi" shell will process and program exit, it will not proceed further
Manikandan Raj
sumber