Saya mengamati beberapa perilaku aneh saat menggunakan set -e
( errexit
), set -u
( nounset
) bersama dengan perangkap ERR dan EXIT. Mereka tampaknya terkait, sehingga menempatkan mereka dalam satu pertanyaan tampaknya masuk akal.
1) set -u
tidak memicu jebakan ERR
Kode:
#!/bin/bash trap 'echo "ERR (rc: $?)"' ERR set -u echo ${UNSET_VAR}
- Diharapkan: ERR trap dipanggil, RC! = 0
- Aktual: ERR trap tidak dipanggil, RC == 1
- Catatan:
set -e
tidak mengubah hasilnya
2) Menggunakan set -eu
kode keluar dalam perangkap EXIT adalah 0 bukan 1
Kode:
#!/bin/bash trap 'echo "EXIT (rc: $?)"' EXIT set -eu echo ${UNSET_VAR}
- Diharapkan: EXIT trap dipanggil, RC == 1
- Aktual: EXIT trap disebut, RC == 0
- Catatan: Saat menggunakan
set +e
, RC == 1. Perangkap EXIT mengembalikan RC yang tepat saat ada perintah lain yang melempar kesalahan. - Sunting: Ada posting SO tentang topik ini dengan komentar menarik yang menunjukkan bahwa ini mungkin terkait dengan versi Bash yang digunakan. Menguji cuplikan ini dengan Bash 4.3.11 menghasilkan RC = 1, jadi itu lebih baik. Sayangnya meningkatkan Bash (dari 3.2.51) pada semua host tidak mungkin saat ini, jadi kami harus datang dengan beberapa solusi lain.
Adakah yang bisa menjelaskan perilaku ini?
Pencarian topik-topik ini tidak terlalu berhasil, yang agak mengejutkan mengingat jumlah posting pada pengaturan dan jebakan Bash. Ada satu utas forum , tetapi kesimpulannya agak tidak memuaskan.
bash
putus dengan standar dan mulai menempatkan jebakan dalam subkulit. Perangkap seharusnya dieksekusi di lingkungan yang sama dari mana datang kembalinya, tetapibash
belum melakukan itu untuk sementara waktu.set -e
danset -u
keduanya dirancang khusus untuk membunuh shell scripted. Menggunakan mereka dalam kondisi yang dapat memicu aplikasi mereka akan mematikan shell scripted. Tidak ada jalan keluarnya, kecuali untuk tidak menggunakannya, dan sebagai gantinya untuk menguji kondisi-kondisi tersebut ketika mereka berlaku dalam urutan kode. Jadi, pada dasarnya, Anda dapat menulis kode-shell yang baik, atau dapat Anda gunakanset -eu
.-u
tidak akan memicu perangkap ERR (ini adalah kesalahan, jadi seharusnya tidak memicu perangkap) atau kode kesalahan adalah 0 bukannya 1. yang terakhir sepertinya adalah bug yang sudah diperbaiki di versi yang lebih baru, jadi begitu. Tetapi bagian pertama cukup sulit untuk dipahami jika Anda belum menyadari bahwa kesalahan dalam evaluasi shell (ekspansi parameter) dan kesalahan aktual dalam perintah tampaknya merupakan dua hal yang berbeda. Untuk solusinya, seperti yang Anda sarankan, saya sekarang mencoba untuk menghindari-eu
dan memeriksa secara manual ketika diperlukan(set -u; : $UNSET_VAR)
dan serupa. Hal-hal semacam ini bisa bagus juga - Anda dapat menjatuhkan banyak sekali-sekali&&
:,(set -e; mkdir dir; cd dir; touch dirfile)
jika Anda mengerti maksud saya. Hanya saja itu adalah konteks yang terkontrol - ketika Anda menetapkannya sebagai opsi global, Anda kehilangan kendali dan menjadi terkontrol. Namun, biasanya ada solusi yang lebih efisien.Jawaban:
Dari
man bash
:set -u
"@"
dan"*"
sebagai kesalahan saat melakukan ekspansi parameter. Jika ekspansi dilakukan pada variabel atau parameter yang tidak disetel, shell mencetak pesan kesalahan, dan, jika tidak-i
nteraktif, keluar dengan status bukan nol.POSIX menyatakan bahwa, jika terjadi kesalahan ekspansi , shell non-interaktif akan keluar ketika ekspansi dikaitkan dengan shell khusus builtin (yang merupakan pembedaan yang
bash
secara teratur diabaikan, dan karenanya mungkin tidak relevan) atau utilitas lain selain ."${x!y}"
karena!
tidak operator valid) ; suatu implementasi dapat memperlakukan ini sebagai kesalahan sintaksis jika itu dapat mendeteksi mereka selama tokenization, daripada selama ekspansi.Juga dari
man bash
:trap ... ERR
while
atauuntil
...if
pernyataan ...&&
atau||
kecuali perintah yang mengikuti akhir&&
atau||
...!
.-e
.Perhatikan di atas bahwa perangkap ERR adalah semua tentang evaluasi pengembalian perintah lain . Tetapi ketika kesalahan ekspansi terjadi, tidak ada perintah yang dijalankan untuk mengembalikan apa pun. Dalam contoh Anda,
echo
tidak pernah terjadi - karena sementara shell mengevaluasi dan memperluas argumennya, ia menemukan-u
variabel nset, yang telah ditentukan oleh opsi shell eksplisit untuk menyebabkan keluar langsung dari shell saat ini, scripted.Dan perangkap EXIT , jika ada, dieksekusi, dan shell keluar dengan pesan diagnostik dan status keluar selain 0 - persis seperti yang seharusnya dilakukan.
Adapun hal rc: 0 , saya berharap itu adalah bug versi tertentu dari beberapa jenis - mungkin berkaitan dengan dua pemicu untuk EXIT yang terjadi pada saat yang sama dan yang satu mendapatkan kode keluar yang lain (yang seharusnya tidak terjadi) . Lagi pula, dengan
bash
biner terbaru yang diinstal olehpacman
:Saya menambahkan baris pertama sehingga Anda dapat melihat bahwa kondisi shell adalah kondisi shell yang scripted - tidak interaktif. Outputnya adalah:
Berikut adalah beberapa catatan yang relevan dari daftar perubahan terbaru :
$?
dengan benar.for
perintah memiliki nomor baris yang salah.trap
di-pable dalam perintah subshell asynchronous.trap
penangan untuk sinyal-sinyal itu, dan memungkinkan sebagian besartrap
penangan untuk dijalankan secara rekursif (menjalankantrap
penangan saattrap
penangan menjalankan) .Saya pikir yang terakhir atau yang paling relevan - atau mungkin kombinasi keduanya. Sebuah
trap
handler adalah dengan sifatnya asynchronous karena seluruh pekerjaan adalah untuk menunggu dan menangani sinyal asynchronous . Dan Anda memicu dua secara bersamaan dengan-eu
dan$UNSET_VAR
.Dan mungkin Anda harus memperbarui, tetapi jika Anda menyukai diri Anda sendiri, Anda akan melakukannya dengan shell yang berbeda sama sekali.
sumber
(Saya menggunakan bash 4.2.53). Untuk bagian 1, halaman bash man hanya mengatakan "Pesan kesalahan akan ditulis ke kesalahan standar, dan shell non-interaktif akan keluar". Itu tidak mengatakan perangkap ERR akan dipanggil, meskipun saya setuju itu akan berguna jika itu benar.
Untuk menjadi pragmatis, jika apa yang Anda inginkan adalah mengatasi lebih bersih dengan variabel yang tidak terdefinisi, solusi yang mungkin adalah dengan meletakkan sebagian besar kode Anda di dalam suatu fungsi, kemudian jalankan fungsi itu dalam sub-shell dan pulihkan kode kembali dan output stderr. Berikut ini contoh fungsi "cmd ()":
Di bash saya, saya mengerti
sumber