Bagaimana cara mendapatkan nilai output dan keluar dari subkulit saat menggunakan "bash -e"?

70

Pertimbangkan kode berikut

outer-scope.sh

#!/bin/bash
set -e
source inner-scope.sh
echo $(inner)
echo "I thought I would've died :("

inner-scope.sh

#!/bin/bash
function inner() { echo "winner"; return 1; }

Saya mencoba outer-scope.shkeluar ketika ada panggilan inner()gagal. Karena $()memanggil sub-shell, ini tidak terjadi.

Bagaimana lagi cara saya mendapatkan output dari suatu fungsi sambil menjaga fakta bahwa fungsi tersebut dapat keluar dengan kode keluar yang tidak nol?

jabalsad
sumber

Jawaban:

102

$()mempertahankan status keluar; Anda hanya perlu menggunakannya dalam pernyataan yang tidak memiliki status sendiri, seperti tugas.

output = $ (dalam)

Setelah ini, $?akan berisi status keluar dari inner, dan Anda dapat menggunakan semua jenis cek untuk itu:

output=$(inner) || exit $?
echo $output

Atau:

if ! output=$(inner); then
    exit $?
fi
echo $output

Atau:

if output=$(inner); then
    echo $output
else
    exit $?
fi

(Catatan: Telanjang exittanpa argumen sama dengan exit $?- yaitu, ia keluar dengan status keluar perintah terakhir. Saya menggunakan bentuk kedua hanya untuk kejelasan.)


Juga, sebagai catatan: sourcesama sekali tidak terkait dalam kasus ini. Anda bisa mendefinisikan inner()dalam outer-scope.shfile, dengan hasil yang sama.

grawity
sumber
Mengapa begitu, meskipun $? berisi status keluar dari $ () yang naskahnya tidak keluar secara otomatis (mengingat bahwa -e diatur)? EDIT: Nevermind, saya pikir Anda telah menjawab pertanyaan saya, terima kasih!
jabalsad
Saya tidak yakin. (Saya belum menguji salah satu di atas.) Tetapi ada beberapa batasan pada -e, semua dijelaskan di halaman manual bash; juga, jika Anda bertanya tentang echo $(), maka itu mungkin karena kode keluar subkulit diabaikan ketika baris - echoperintah - memiliki kode keluar (biasanya 0) sendiri.
grawity
1
Hmm, saat saya mengetik if ! $(exit 1) ; then echo $?; fi, saya mengerti 0. Tidak yakin ifcara untuk pergi jika Anda perlu mempertahankan nilai keluar itu.
Ron Burk
@grawity - Apakah ini berfungsi dengan Dash? Saya mencoba untuk mengatasi beberapa perilaku Autoconf yang mengganggu (yaitu, keberhasilan pelaporan Autoconf ketika Sun atau kompiler IBM mencetak opsi ilegal ke terminal).
jww
3
if ! output=$(inner); then exit $?; fiakan keluar dengan kode kembali 0 karena $?akan memberikan kode kembali !bukan kode kembali inner. Anda bisa mendapatkan perilaku yang diinginkan if output=$(inner); then : ; else exit $?; fitetapi itu jelas lebih bertele
SJL
26

Lihat BashFAQ / 002 :

Jika Anda menginginkan keduanya (output, dan status keluar):

output=$(command)
status=$? 

Kasus Khusus

Catatan tentang kasing rumit dengan variabel fungsi lokal, bandingkan kode berikut:

f() { local    v=$(echo data; false); echo output:$v, status:$?; }
g() { local v; v=$(echo data; false); echo output:$v, status:$?; }

Kita akan mendapatkan:

$ f     # fooled by 'local' with inline initialization
output:data, status:0

$ g     # a good one
output:data, status:1

Mengapa?

Ketika output dari sebuah subkulit digunakan untuk menginisialisasi localvariabel, status keluar tidak lagi dari subkulit, tetapi dari perintah , yang paling mungkin .local 0

Lihat juga https://stackoverflow.com/a/4421282/537554

Ryenus
sumber
3
Meskipun ini tidak benar-benar menjawab pertanyaan, ini berguna bagi saya hari ini, jadi +1.
fourpastmidnight
1
Status keluar untuk perintah bash selalu dari perintah terakhir yang dijalankan. Ketika kita menghabiskan begitu banyak waktu dalam bahasa yang sangat diketik, mudah untuk melupakan bahwa "lokal" bukan tipe specifier tetapi hanya perintah lain. Terima kasih telah mengulangi poin ini di sini, membantu saya hari ini.
markeissler
2
Wow, saya mengalami masalah persis ini sekarang dan Anda membereskannya. Terima kasih!
krb686
4
#!/bin/bash
set -e
source inner-scope.sh
foo=$(inner)
echo $foo
echo "I thought I would've died :("

Dengan menambahkan echo, subkulit tidak berdiri sendiri (tidak dicentang secara terpisah) dan tidak dibatalkan. Tugas menghindari masalah ini.

Anda juga dapat melakukan ini, dan mengarahkan output ke file, untuk memprosesnya nanti.

tmpfile=$( mktemp )
inner > $tmpfile
cat $tmpfile
rm $tmpfile
Daniel Beck
sumber
Tentu saja, $ tmpfile terus ada dalam varian kedua ...
Daniel Beck