Jalankan hal di atas dalam bashatau zshdan Anda akan mendapatkan hasil yang sama; hanya satu dari retval_bashdan retval_zshakan ditetapkan. Yang lain akan kosong. Ini akan memungkinkan fungsi berakhir dengan return $retval_bash $retval_zsh(perhatikan kurangnya tanda kutip!).
Dan pipestatusdi zsh. Sayangnya cangkang lain tidak memiliki fitur ini.
Gilles
8
Catatan: Array di zsh mulai secara berlawanan pada indeks 1, jadi itu echo "$pipestatus[1]" "$pipestatus[2]".
Christoph Wurm
5
Anda dapat memeriksa seluruh saluran pipa seperti ini:if [ `echo "${PIPESTATUS[@]}" | tr -s ' ' + | bc` -ne 0 ]; then echo FAIL; fi
l0b0
7
@ JanHudec: Mungkin Anda harus membaca lima kata pertama dari jawaban saya. Juga tunjukkan di mana pertanyaan meminta jawaban khusus POSIX.
camh
12
@ JanHudec: Juga tidak ditandai POSIX. Mengapa Anda menganggap jawabannya pasti POSIX? Itu tidak ditentukan sehingga saya memberikan jawaban yang memenuhi syarat. Tidak ada yang salah dengan jawaban saya, plus ada beberapa jawaban untuk mengatasi kasus lain.
camh
238
Ada 3 cara umum untuk melakukan ini:
Pipefail
Cara pertama adalah mengatur pipefailopsi ( ksh, zshatau bash). Ini adalah yang paling sederhana dan apa yang dilakukannya pada dasarnya adalah mengatur status keluar $?ke kode keluar dari program terakhir untuk keluar non-nol (atau nol jika semua keluar dengan sukses).
@ Patrick solusi pipestatus bekerja pada bash, hanya lebih banyak quastion jika saya menggunakan skrip ksh Anda pikir kita dapat menemukan sesuatu yang mirip dengan pipestatus? , (sementara ini saya melihat pipestatus tidak didukung oleh ksh)
yael
2
@ Ohel, saya tidak menggunakan ksh, tapi dari pandangan singkat di halaman manualnya, itu tidak mendukung $PIPESTATUSatau yang serupa. Itu mendukung pipefailopsi itu.
Patrick
1
Saya memutuskan untuk menggunakan pipefail karena memungkinkan saya untuk mendapatkan status dari perintah yang gagal di sini:LOG=$(failed_command | successful_command)
vmrob
51
Solusi ini berfungsi tanpa menggunakan fitur spesifik bash atau file sementara. Bonus: pada akhirnya status keluar sebenarnya adalah status keluar dan bukan string dalam file.
Situasi:
someprog | filter
Anda menginginkan status keluar dari someprogdan keluaran dari filter.
hasil dari konstruk ini adalah stdout dari filtersebagai stdout dari konstruk dan status keluar dari someprogsebagai status keluar dari konstruk.
konstruksi ini juga berfungsi dengan pengelompokan perintah sederhana {...}alih-alih subkulit (...). subkulit memiliki beberapa implikasi, antara lain biaya kinerja, yang tidak kita butuhkan di sini. baca manual bash halus untuk detail lebih lanjut: https://www.gnu.org/software/bash/manual/html_node/Command-Grouping.html
Catatan: proses anak mewarisi deskriptor file terbuka dari induknya. Itu berarti someprogakan mewarisi deskriptor file terbuka 3 dan 4. Jika someprogmenulis ke file deskriptor 3 maka itu akan menjadi status keluar. Status keluar sebenarnya akan diabaikan karena readhanya dibaca sekali.
Jika Anda khawatir bahwa Anda someprogmungkin menulis ke deskriptor 3 atau 4, maka yang terbaik adalah menutup deskriptor file sebelum memanggil someprog.
The exec 3>&- 4>&-sebelum someprogmenutup file descriptor sebelum mengeksekusi someprogjadi untuk someprogfile yang mereka deskriptor sama sekali tidak ada.
Subkulit dibuat dengan deskriptor file 4 diarahkan ke stdout. Ini berarti bahwa apa pun yang dicetak ke file descriptor 4 di subkulit akan berakhir sebagai stdout dari keseluruhan konstruk.
Pipa dibuat dan perintah di sebelah kiri ( #part3) dan kanan ( #part2) dieksekusi. exit $xsjuga merupakan perintah terakhir dari pipa dan itu berarti string dari stdin akan menjadi status keluar dari keseluruhan konstruk.
Subkulit dibuat dengan deskriptor file 3 diarahkan ke stdout. Ini berarti bahwa apa pun yang dicetak ke file descriptor 3 di subkulit ini akan berakhir #part2dan pada gilirannya akan menjadi status keluar dari keseluruhan konstruk.
Pipa dibuat dan perintah di sebelah kiri ( #part5dan #part6) dan kanan ( filter >&4) dieksekusi. Output dari filterdiarahkan ke file deskriptor 4. Pada #part1file deskriptor 4 diarahkan ke stdout. Ini berarti bahwa output filteradalah stdout dari keseluruhan konstruk.
Status keluar dari #part6dicetak ke file deskriptor 3. Pada #part3file deskriptor 3 dialihkan ke #part2. Ini berarti bahwa status keluar dari #part6akan menjadi status keluar akhir untuk keseluruhan konstruk.
someprogdieksekusi. Status keluar diambil #part5. Stdout diambil oleh pipa masuk #part4dan diteruskan ke filter. Output dari filterpada gilirannya akan mencapai stdout seperti yang dijelaskan dalam#part4
set -o pipefaildi dalam skrip harus lebih kuat, misalnya jika seseorang mengeksekusi skrip via bash foo.sh.
maxschlepzig
Bagaimana cara kerjanya? apakah kamu punya contoh
Johan
2
Perhatikan bahwa -o pipefailtidak ada dalam POSIX.
scy
2
Ini tidak berfungsi di BASH 3.2.25 (1) saya - dirilis. Di atas / tmp / ff saya miliki #!/bin/bash -o pipefail. Kesalahan adalah:/bin/bash: line 0: /bin/bash: /tmp/ff: invalid option name
Felipe Alvarez
2
@FelipeAlvarez: Beberapa lingkungan (termasuk linux) tidak mem-parsing spasi pada #!garis di luar yang pertama, dan ini menjadi /bin/bash-o pipefail/tmp/ff, alih-alih parsing yang diperlukan /bin/bash-opipefail/tmp/ff- getopt(atau serupa) menggunakan optarg, yang merupakan item berikutnya ARGV, sebagai argumen untuk -o, jadi gagal. Jika Anda membuat pembungkus (katakan, bash-pfitu baru saja dilakukan exec /bin/bash -o pipefail "$@", dan letakkan di #!telepon, itu akan berhasil. Lihat juga: en.wikipedia.org/wiki/Shebang_%28Unix%29
lindes
22
Apa yang saya lakukan jika memungkinkan adalah untuk memberi makan kode keluar dari fooke bar. Misalnya, jika saya tahu bahwa footidak pernah menghasilkan garis hanya dengan angka, maka saya bisa menempelkan kode keluar:
Ini selalu bisa dilakukan jika ada beberapa cara baruntuk bekerja pada semua kecuali baris terakhir, dan meneruskan baris terakhir sebagai kode keluarnya.
Jika barmerupakan pipeline kompleks yang outputnya tidak Anda butuhkan, Anda dapat mem-bypass sebagiannya dengan mencetak kode keluar pada deskriptor file yang berbeda.
exit_codes=$({{ foo; echo foo:"$?">&3;}|{ bar >/dev/null; echo bar:"$?">&3;}}3>&1)
Setelah ini $exit_codesbiasanya foo:X bar:Y, tetapi bisa jadi bar:Y foo:Xjika barberhenti sebelum membaca semua inputnya atau jika Anda beruntung. Saya pikir menulis ke pipa hingga 512 byte adalah atom pada semua unix, sehingga bagian foo:$?- bar:$?bagiannya tidak akan dicampur selama tag string berada di bawah 507 byte.
Jika Anda perlu mengambil output dari bar, itu menjadi sulit. Anda dapat menggabungkan teknik-teknik di atas dengan mengatur agar bartidak pernah mengandung garis yang terlihat seperti indikasi kode keluar, tetapi ia mendapatkan fiddly.
output=$(echo;{{ foo; echo foo:"$?">&3;}|{ bar | sed 's/^/^/'; echo bar:"$?">&3;}}3>&1)
nl='
'
foo_exit_code=${output#*${nl}foo:}; foo_exit_code=${foo_exit_code%%$nl*}
bar_exit_code=${output#*${nl}bar:}; bar_exit_code=${bar_exit_code%%$nl*}
output=$(printf %s "$output"| sed -n 's/^\^//p')
Dan, tentu saja, ada opsi sederhana menggunakan file sementara untuk menyimpan status. Sederhana, tapi tidak yang sederhana dalam produksi:
Jika ada beberapa skrip yang berjalan secara bersamaan, atau jika skrip yang sama menggunakan metode ini di beberapa tempat, Anda perlu memastikan mereka menggunakan nama file sementara yang berbeda.
Membuat file sementara dengan aman di direktori bersama itu sulit. Seringkali, /tmpadalah satu-satunya tempat di mana skrip pasti dapat menulis file. Gunakan mktemp, yang bukan POSIX tetapi tersedia di semua unit serius saat ini.
Saat menggunakan pendekatan file sementara, saya lebih suka menambahkan jebakan untuk EXIT yang menghapus semua file sementara sehingga tidak ada sampah yang tersisa bahkan jika skripnya mati
miracle173
17
Mulai dari pipa:
foo | bar | baz
Berikut ini adalah solusi umum menggunakan hanya shell POSIX dan tidak ada file sementara:
$error_statuses berisi kode status dari setiap proses yang gagal, dalam urutan acak, dengan indeks untuk mengetahui perintah mana yang dipancarkan setiap status.
# if "bar" failed, output its status:
echo "$error_statuses"| grep '1:'| cut -d:-f2
# test if all commands succeeded:
test -z "$error_statuses"# test if the last command succeeded:! echo "$error_statuses"| grep '2:'>/dev/null
Catat kutipan di $error_statusesdalam tes saya; tanpa mereka greptidak dapat dibedakan karena baris baru dipaksa ke ruang.
Jadi saya ingin menyumbangkan jawaban seperti lesmana, tetapi saya pikir jawaban saya mungkin sedikit lebih sederhana dan sedikit lebih menguntungkan dari solusi Bourne-shell:
# You want to pipe command1 through command2:
exec 4>&1
exitstatus=`{ { command1; printf $? 1>&3; } | command2 1>&4; } 3>&1`# $exitstatus now has command1's exit status.
Saya pikir ini paling baik dijelaskan dari dalam ke luar - command1 akan mengeksekusi dan mencetak output regulernya di stdout (file descriptor 1), kemudian setelah selesai, printf akan mengeksekusi dan mencetak kode keluar command1 pada stdout, tetapi stdout diarahkan ke deskriptor file 3.
Ketika command1 sedang berjalan, stdout-nya sedang dipipakan ke command2 (output printf tidak pernah membuatnya ke command2 karena kita mengirimkannya ke file descriptor 3, bukan 1, yang dibaca oleh pipa). Kemudian kita mengarahkan output command2 ke file descriptor 4, sehingga ia juga tetap keluar dari file descriptor 1 - karena kita ingin file descriptor 1 gratis sebentar kemudian, karena kita akan membawa output printf pada file deskriptor 3 kembali ke file descriptor 1 - karena itulah substitusi perintah (backticks), akan menangkap dan itulah yang akan ditempatkan ke dalam variabel.
Bit terakhir sihir adalah yang pertama exec 4>&1kita lakukan sebagai perintah terpisah - itu membuka file descriptor 4 sebagai salinan stdout shell eksternal. Substitusi perintah akan menangkap apa pun yang tertulis pada standar keluar dari perspektif perintah di dalamnya - tetapi, karena output command2 akan mengajukan deskriptor 4 sejauh menyangkut substitusi perintah, substitusi perintah tidak menangkapnya - namun, setelah "keluar" dari substitusi perintah, itu secara efektif masih pergi ke file deskriptor keseluruhan skrip 1.
(Itu exec 4>&1harus menjadi perintah terpisah karena banyak shell umum tidak suka ketika Anda mencoba menulis ke deskriptor file di dalam substitusi perintah, yang dibuka di perintah "eksternal" yang menggunakan substitusi. Jadi ini adalah cara portabel paling sederhana untuk melakukannya.)
Anda dapat melihatnya dengan cara yang kurang teknis dan lebih lucu, seolah-olah output dari perintah saling melompati satu sama lain: command1 pipa ke command2, maka output printf melompati perintah 2 sehingga perintah2 tidak menangkapnya, dan kemudian output perintah 2 melompati dan keluar dari substitusi perintah sama seperti printf mendarat tepat pada waktunya untuk ditangkap oleh substitusi sehingga berakhir di variabel, dan output command2 berjalan dengan cara yang menyenangkan ditulis ke output standar, sama seperti dalam pipa normal.
Juga, seperti yang saya mengerti, $?masih akan berisi kode kembali dari perintah kedua di dalam pipa, karena penugasan variabel, penggantian perintah, dan perintah majemuk semuanya secara efektif transparan ke kode pengembalian dari perintah di dalamnya, sehingga status pengembalian dari command2 harus disebarkan - ini, dan tidak harus mendefinisikan fungsi tambahan, itulah mengapa saya pikir ini mungkin solusi yang agak lebih baik daripada yang diusulkan oleh lesmana.
Menurut peringatan lesmana, ada kemungkinan bahwa command1 pada akhirnya akan menggunakan deskriptor file 3 atau 4, jadi agar lebih kuat, Anda akan melakukan:
Perhatikan bahwa saya menggunakan perintah majemuk dalam contoh saya, tetapi subkulit (menggunakan ( )alih-alih { }juga akan berfungsi, meskipun mungkin kurang efisien.)
Perintah mewarisi deskriptor file dari proses yang meluncurkannya, sehingga seluruh baris kedua akan mewarisi deskriptor file empat, dan perintah majemuk diikuti oleh 3>&1akan mewarisi deskriptor file tiga. Jadi 4>&-memastikan bahwa perintah inner compound tidak akan mewarisi file descriptor empat, dan 3>&-tidak akan mewarisi file deskriptor tiga, jadi command1 mendapat lingkungan yang lebih bersih dan lebih standar. Anda juga bisa memindahkan bagian dalam 4>&-ke sebelah 3>&-, tapi saya pikir mengapa tidak membatasi ruang lingkupnya sebanyak mungkin.
Saya tidak yakin seberapa sering hal-hal menggunakan deskriptor file tiga dan empat secara langsung - Saya pikir sebagian besar program waktu menggunakan syscalls yang mengembalikan deskriptor file yang tidak digunakan saat ini, tetapi kadang-kadang kode menulis ke deskriptor file 3 secara langsung, saya tebak (saya bisa membayangkan sebuah program memeriksa deskriptor file untuk melihat apakah itu terbuka, dan menggunakannya jika ada, atau berperilaku berbeda sesuai jika tidak). Jadi yang terakhir mungkin terbaik untuk diingat dan digunakan untuk kasus-kasus tujuan umum.
Terlihat menarik, tapi saya tidak tahu apa yang Anda harapkan dari perintah ini, dan komputer saya juga tidak bisa; Saya mengerti -bash: 3: Bad file descriptor.
G-Man
@ G-Man Benar, saya terus lupa bash tidak tahu apa yang dilakukannya ketika datang ke file deskriptor, tidak seperti shell yang biasanya saya gunakan (abu yang datang dengan busybox). Saya akan memberi tahu Anda ketika saya memikirkan solusi yang membuat bash senang. Sementara itu jika Anda memiliki kotak debian berguna Anda dapat mencobanya di dash, atau jika Anda punya busybox berguna Anda dapat mencobanya dengan busybox ash / sh.
mtraceur
@ G-Man Seperti apa yang saya harapkan dilakukan oleh perintah, dan apa yang dilakukan di shell lain, adalah mengarahkan stdout dari command1 sehingga tidak tertangkap oleh substitusi perintah, tetapi begitu di luar substitusi perintah, ia turun fd3 kembali ke stdout sehingga disalurkan seperti yang diharapkan ke command2. Ketika command1 keluar, printf menembak dan mencetak status keluarnya, yang ditangkap ke dalam variabel oleh substitusi perintah. Rincian yang sangat terperinci di sini: stackoverflow.com/questions/985876/tee-and-exit-status/... Juga, komentar Anda itu terbaca seolah-olah dimaksudkan untuk menghina?
mtraceur
Di mana saya harus mulai? (1) Saya minta maaf jika Anda merasa terhina. "Terlihat menarik" dimaksudkan dengan sungguh-sungguh; alangkah baiknya jika sesuatu yang ringkas seperti itu bekerja sebaik yang Anda harapkan. Di luar itu, saya katakan, secara sederhana, bahwa saya tidak mengerti apa yang seharusnya dilakukan oleh solusi Anda. Saya telah bekerja / bermain dengan Unix untuk waktu yang lama (sejak sebelum Linux ada), dan, jika saya tidak mengerti sesuatu, itu adalah bendera merah yang, mungkin, orang lain tidak akan memahaminya juga, dan bahwa itu perlu penjelasan lebih lanjut (IMNSHO). … (Lanjutan)
G-Man
(Lanjutkan) ... Karena Anda "... suka berpikir ... bahwa [Anda] memahami segala sesuatu lebih daripada orang kebanyakan", mungkin Anda harus ingat bahwa tujuan dari Stack Exchange bukanlah untuk menjadi layanan penulisan perintah, mengaduk-aduk ribuan solusi satu kali untuk pertanyaan-pertanyaan sepele yang berbeda; melainkan mengajarkan orang bagaimana memecahkan masalah mereka sendiri. Dan, untuk itu, mungkin Anda perlu menjelaskan hal-hal dengan cukup baik sehingga "orang biasa" dapat memahaminya. Lihatlah jawaban lesmana untuk contoh penjelasan yang sangat bagus. … (Lanjutan)
G-Man
11
Jika Anda menginstal paket moreutils, Anda dapat menggunakan utilitas mispipe yang melakukan persis seperti yang Anda minta.
solusi lesmana di atas juga dapat dilakukan tanpa overhead memulai subproses bersarang dengan menggunakan { .. }sebagai gantinya (mengingat bahwa bentuk perintah yang dikelompokkan ini selalu harus diakhiri dengan titik koma). Sesuatu seperti ini:
Saya telah memeriksa konstruk ini dengan versi dash 0.5.5 dan versi bash 3.2.25 dan 4.2.42, jadi walaupun beberapa shell tidak mendukung { .. }pengelompokan, itu masih sesuai dengan POSIX.
Ini berfungsi dengan sangat baik pada kebanyakan shell yang pernah saya coba, termasuk NetBSD sh, pdksh, mksh, dash, bash. Namun saya tidak bisa menjalankannya dengan AT&T Ksh (93s +, 93u +) atau zsh (4.3.9, 5.2), bahkan dengan set -o pipefaildi ksh atau sejumlah waitperintah yang dipercikkan di salah satu. Saya pikir itu mungkin, sebagian setidaknya, menjadi masalah parsing untuk ksh, seolah-olah saya tetap menggunakan subshell maka itu berfungsi dengan baik, tetapi bahkan dengan ifmemilih varian subkulit untuk ksh tetapi meninggalkan perintah majemuk untuk orang lain, itu gagal .
Greg A. Woods
4
Ini portabel, yaitu berfungsi dengan shell yang sesuai dengan POSIX, tidak mengharuskan direktori saat ini dapat ditulis dan memungkinkan banyak skrip menggunakan trik yang sama untuk dijalankan secara bersamaan.
Ini tidak berhasil karena beberapa alasan. 1. File sementara dapat dibaca sebelum ditulis. 2. Membuat file sementara di direktori bersama dengan nama yang dapat diprediksi tidak aman (DoS trivial, ras symlink). 3. Jika skrip yang sama menggunakan trik ini beberapa kali, itu akan selalu menggunakan nama file yang sama. Untuk menyelesaikan 1, baca file setelah pipa selesai. Untuk menyelesaikan 2 dan 3, gunakan file sementara dengan nama yang dibuat secara acak atau dalam direktori pribadi.
Gilles
+1 Nah, $ {PIPESTATUS [0]} lebih mudah tetapi ide dasar di sini berfungsi jika ada yang tahu tentang masalah yang disebutkan Gilles.
Johan
Anda dapat menyimpan beberapa subkulit: (s=/tmp/.$$_$RANDOM;{foo;echo $?>$s;}|bar; exit $(cat $s;rm $s)). @ Johan: Saya setuju itu lebih mudah dengan Bash tetapi dalam beberapa konteks, mengetahui bagaimana menghindari Bash sepadan.
dubiousjim
4
Mengikuti ini dimaksudkan sebagai tambahan untuk jawaban @ Patrik, jika Anda tidak dapat menggunakan salah satu solusi umum.
Jawaban ini mengasumsikan sebagai berikut:
Anda memiliki shell yang tidak tahu $PIPESTATUSatau tidakset -o pipefail
Anda ingin menggunakan pipa untuk eksekusi paralel, jadi tidak ada file sementara.
Anda tidak ingin ada kekacauan tambahan di sekitar jika Anda mengganggu skrip, mungkin dengan pemadaman listrik mendadak.
Solusi ini harus relatif mudah diikuti dan bersih untuk dibaca.
Anda tidak ingin memperkenalkan subkulit tambahan.
Anda tidak dapat mengutak-atik file deskriptor yang ada, jadi stdin / out / err tidak boleh disentuh (namun Anda dapat memperkenalkan beberapa yang baru sementara)
Asumsi tambahan. Anda dapat menyingkirkan semuanya, tetapi resep ini terlalu banyak, sehingga tidak dibahas di sini:
Yang ingin Anda ketahui adalah bahwa semua perintah di PIPE memiliki kode keluar 0.
Anda tidak memerlukan informasi band samping tambahan.
Shell Anda memang menunggu semua perintah pipa untuk kembali.
Sebelum:, foo | bar | baznamun ini hanya mengembalikan kode keluar dari perintah terakhir ( baz)
Dicari: $?tidak boleh 0(benar), jika ada perintah di pipa gagal
Setelah:
TMPRESULTS="`mktemp`"{
rm -f "$TMPRESULTS"{ foo || echo $?>&9;}|{ bar || echo $?>&9;}|{ baz || echo $?>&9;}#wait! read TMPRESULTS <&8}9>>"$TMPRESULTS"8<"$TMPRESULTS"# $? now is 0 only if all commands had exit code 0
Dijelaskan:
Tempfile dibuat dengan mktemp. Ini biasanya segera membuat file dalam/tmp
Tempfile ini kemudian dialihkan ke FD 9 untuk menulis dan FD 8 untuk dibaca
Kemudian tempfile segera dihapus. Tetap terbuka, meskipun, sampai kedua FD keluar dari keberadaan.
Sekarang pipa dimulai. Setiap langkah hanya menambah FD 9, jika ada kesalahan.
The waitdiperlukan untuk ksh, karena kshyang lain tidak menunggu semua pipa perintah untuk menyelesaikan. Namun harap dicatat bahwa ada efek samping yang tidak diinginkan jika ada tugas latar belakang, jadi saya berkomentar secara default. Jika menunggu tidak sakit, Anda dapat berkomentar.
Setelah itu isi file dibaca. Jika kosong (karena semua berfungsi) readkembali false, maka truetunjukkan kesalahan
Ini dapat digunakan sebagai pengganti plugin untuk satu perintah dan hanya perlu mengikuti:
FD 9 dan 8 yang tidak digunakan
Variabel lingkungan tunggal untuk menyimpan nama tempfile
Dan resep ini dapat disesuaikan dengan hampir semua shell di luar sana yang memungkinkan pengalihan IO
Juga cukup agnostik platform dan tidak perlu hal-hal seperti /proc/fd/N
Bug:
Script ini memiliki bug jika /tmpkehabisan ruang. Jika Anda memerlukan perlindungan terhadap kasing buatan ini, Anda juga dapat melakukannya sebagai berikut, namun ini memiliki kekurangan, bahwa jumlah 0in 000tergantung pada jumlah perintah dalam pipa, sehingga sedikit lebih rumit:
kshdan shell serupa yang hanya menunggu perintah pipa terakhir perlu waituncommented
Contoh terakhir menggunakan printf "%1s" "$?"bukan echo -n "$?"karena ini lebih portabel. Tidak semua platform mengartikan -ndengan benar.
printf "$?"akan melakukannya juga, namun printf "%1s"menangkap beberapa kasus sudut jika Anda menjalankan skrip pada beberapa platform yang benar-benar rusak. (Baca: jika Anda kebetulan memprogram masuk paranoia_mode=extreme.)
FD 8 dan FD 9 dapat lebih tinggi pada platform yang mendukung banyak digit. SETELAH shell konforman POSIX hanya perlu mendukung satu digit.
Diuji dengan Debian 8.2 sh, bash, ksh, ash, sashdan bahkancsh
Bagaimana dengan pembersihan seperti jlliagre? Apakah Anda tidak meninggalkan file yang disebut status foo?
Johan
@Johan: Jika Anda lebih suka saran saya, jangan ragu untuk memilihnya;) Selain tidak meninggalkan file, ini memiliki keunggulan memungkinkan beberapa proses untuk menjalankan ini secara bersamaan dan direktori saat ini tidak perlu ditulis.
jlliagre
2
Blok 'jika' berikut ini akan berjalan hanya jika 'perintah' berhasil:
if command;then# ...fi
Secara khusus, Anda dapat menjalankan sesuatu seperti ini:
Yang akan menjalankan haconf -makerwdan menyimpan stdout dan stderr ke "$ haconf_out". Jika nilai yang dikembalikan dari haconfbenar, maka blok 'jika' akan dieksekusi dan grepakan membaca "$ haconf_out", mencoba mencocokkannya dengan "Cluster sudah dapat ditulisi".
Perhatikan bahwa pipa secara otomatis membersihkan diri mereka sendiri; dengan pengalihan Anda harus berhati-hati untuk menghapus "$ haconf_out" saat selesai.
Bukan sebagai elegan pipefail, tetapi alternatif yang sah jika fungsi ini tidak dalam jangkauan.
(Dengan bash setidaknya) dikombinasikan dengan set -esatu dapat menggunakan subkulit untuk secara eksplisit meniru pipefail dan keluar pada kesalahan pipa
set-e
foo | bar
( exit ${PIPESTATUS[0]})
rest of program
Jadi jika foogagal karena beberapa alasan - sisa program tidak akan dieksekusi dan skrip keluar dengan kode kesalahan yang sesuai. (Ini mengasumsikan bahwa foomencetak kesalahannya sendiri, yang cukup untuk memahami alasan kegagalan)
# =========================================================== ## Preceding a _pipe_ with ! inverts the exit status returned.
ls | bogus_command # bash: bogus_command: command not found
echo $?# 127! ls | bogus_command # bash: bogus_command: command not found
echo $?# 0# Note that the ! does not change the execution of the pipe.# Only the exit status changes.# =========================================================== #
Saya pikir ini tidak berhubungan. Dalam contoh Anda, saya ingin tahu kode keluar ls, bukan membalikkan kode keluarbogus_command
Michael Mrozek
2
Saya sarankan untuk menarik kembali jawaban itu.
maxschlepzig
3
Yah, sepertinya aku idiot. Saya sebenarnya menggunakan ini dalam naskah sebelum berpikir itu melakukan apa yang diinginkan OP. Untung saya tidak menggunakannya untuk hal-hal penting
Jawaban:
Jika Anda menggunakan
bash
, Anda bisa menggunakanPIPESTATUS
variabel array untuk mendapatkan status keluar dari setiap elemen pipa.Jika Anda menggunakan
zsh
, array mereka disebutpipestatus
(hal penting!) Dan indeks array dimulai pada satu:Untuk menggabungkan mereka dalam suatu fungsi dengan cara yang tidak kehilangan nilai:
Jalankan hal di atas dalam
bash
atauzsh
dan Anda akan mendapatkan hasil yang sama; hanya satu dariretval_bash
danretval_zsh
akan ditetapkan. Yang lain akan kosong. Ini akan memungkinkan fungsi berakhir denganreturn $retval_bash $retval_zsh
(perhatikan kurangnya tanda kutip!).sumber
pipestatus
di zsh. Sayangnya cangkang lain tidak memiliki fitur ini.echo "$pipestatus[1]" "$pipestatus[2]"
.if [ `echo "${PIPESTATUS[@]}" | tr -s ' ' + | bc` -ne 0 ]; then echo FAIL; fi
Ada 3 cara umum untuk melakukan ini:
Pipefail
Cara pertama adalah mengatur
pipefail
opsi (ksh
,zsh
ataubash
). Ini adalah yang paling sederhana dan apa yang dilakukannya pada dasarnya adalah mengatur status keluar$?
ke kode keluar dari program terakhir untuk keluar non-nol (atau nol jika semua keluar dengan sukses).$ PIPESTATUS
Bash juga memiliki variabel array bernama
$PIPESTATUS
($pipestatus
inzsh
) yang berisi status keluar dari semua program dalam pipa terakhir.Anda bisa menggunakan contoh perintah ke-3 untuk mendapatkan nilai spesifik dalam pipa yang Anda butuhkan.
Eksekusi terpisah
Ini adalah solusi yang paling sulit. Jalankan setiap perintah secara terpisah dan tangkap statusnya
sumber
ksh
, tapi dari pandangan singkat di halaman manualnya, itu tidak mendukung$PIPESTATUS
atau yang serupa. Itu mendukungpipefail
opsi itu.LOG=$(failed_command | successful_command)
Solusi ini berfungsi tanpa menggunakan fitur spesifik bash atau file sementara. Bonus: pada akhirnya status keluar sebenarnya adalah status keluar dan bukan string dalam file.
Situasi:
Anda menginginkan status keluar dari
someprog
dan keluaran darifilter
.Ini solusinya:
hasil dari konstruk ini adalah stdout dari
filter
sebagai stdout dari konstruk dan status keluar darisomeprog
sebagai status keluar dari konstruk.konstruksi ini juga berfungsi dengan pengelompokan perintah sederhana
{...}
alih-alih subkulit(...)
. subkulit memiliki beberapa implikasi, antara lain biaya kinerja, yang tidak kita butuhkan di sini. baca manual bash halus untuk detail lebih lanjut: https://www.gnu.org/software/bash/manual/html_node/Command-Grouping.htmlSayangnya tata bahasa bash membutuhkan ruang dan titik koma untuk kurung kurawal sehingga konstruksinya menjadi jauh lebih luas.
Untuk sisa teks ini saya akan menggunakan varian subkulit.
Contoh
someprog
danfilter
:Contoh output:
Catatan: proses anak mewarisi deskriptor file terbuka dari induknya. Itu berarti
someprog
akan mewarisi deskriptor file terbuka 3 dan 4. Jikasomeprog
menulis ke file deskriptor 3 maka itu akan menjadi status keluar. Status keluar sebenarnya akan diabaikan karenaread
hanya dibaca sekali.Jika Anda khawatir bahwa Anda
someprog
mungkin menulis ke deskriptor 3 atau 4, maka yang terbaik adalah menutup deskriptor file sebelum memanggilsomeprog
.The
exec 3>&- 4>&-
sebelumsomeprog
menutup file descriptor sebelum mengeksekusisomeprog
jadi untuksomeprog
file yang mereka deskriptor sama sekali tidak ada.Bisa juga ditulis seperti ini:
someprog 3>&- 4>&-
Langkah demi langkah penjelasan konstruk:
Dari bawah ke atas:
#part3
) dan kanan (#part2
) dieksekusi.exit $xs
juga merupakan perintah terakhir dari pipa dan itu berarti string dari stdin akan menjadi status keluar dari keseluruhan konstruk.#part2
dan pada gilirannya akan menjadi status keluar dari keseluruhan konstruk.#part5
dan#part6
) dan kanan (filter >&4
) dieksekusi. Output darifilter
diarahkan ke file deskriptor 4. Pada#part1
file deskriptor 4 diarahkan ke stdout. Ini berarti bahwa outputfilter
adalah stdout dari keseluruhan konstruk.#part6
dicetak ke file deskriptor 3. Pada#part3
file deskriptor 3 dialihkan ke#part2
. Ini berarti bahwa status keluar dari#part6
akan menjadi status keluar akhir untuk keseluruhan konstruk.someprog
dieksekusi. Status keluar diambil#part5
. Stdout diambil oleh pipa masuk#part4
dan diteruskan kefilter
. Output darifilter
pada gilirannya akan mencapai stdout seperti yang dijelaskan dalam#part4
sumber
(read; exit $REPLY)
(exec 3>&- 4>&-; someprog)
disederhanakan menjadisomeprog 3>&- 4>&-
.{ { { { someprog 3>&- 4>&-; echo $? >&3; } | filter >&4; } 3>&1; } | { read xs; exit $xs; }; } 4>&1
Meskipun tidak persis apa yang Anda minta, Anda bisa menggunakannya
sehingga pipa Anda mengembalikan bukan nol kembali terakhir.
mungkin sedikit kurang coding
Edit: Contoh
sumber
set -o pipefail
di dalam skrip harus lebih kuat, misalnya jika seseorang mengeksekusi skrip viabash foo.sh
.-o pipefail
tidak ada dalam POSIX.#!/bin/bash -o pipefail
. Kesalahan adalah:/bin/bash: line 0: /bin/bash: /tmp/ff: invalid option name
#!
garis di luar yang pertama, dan ini menjadi/bin/bash
-o pipefail
/tmp/ff
, alih-alih parsing yang diperlukan/bin/bash
-o
pipefail
/tmp/ff
-getopt
(atau serupa) menggunakanoptarg
, yang merupakan item berikutnyaARGV
, sebagai argumen untuk-o
, jadi gagal. Jika Anda membuat pembungkus (katakan,bash-pf
itu baru saja dilakukanexec /bin/bash -o pipefail "$@"
, dan letakkan di#!
telepon, itu akan berhasil. Lihat juga: en.wikipedia.org/wiki/Shebang_%28Unix%29Apa yang saya lakukan jika memungkinkan adalah untuk memberi makan kode keluar dari
foo
kebar
. Misalnya, jika saya tahu bahwafoo
tidak pernah menghasilkan garis hanya dengan angka, maka saya bisa menempelkan kode keluar:Atau jika saya tahu bahwa output dari
foo
tidak pernah berisi baris hanya dengan.
:Ini selalu bisa dilakukan jika ada beberapa cara
bar
untuk bekerja pada semua kecuali baris terakhir, dan meneruskan baris terakhir sebagai kode keluarnya.Jika
bar
merupakan pipeline kompleks yang outputnya tidak Anda butuhkan, Anda dapat mem-bypass sebagiannya dengan mencetak kode keluar pada deskriptor file yang berbeda.Setelah ini
$exit_codes
biasanyafoo:X bar:Y
, tetapi bisa jadibar:Y foo:X
jikabar
berhenti sebelum membaca semua inputnya atau jika Anda beruntung. Saya pikir menulis ke pipa hingga 512 byte adalah atom pada semua unix, sehingga bagianfoo:$?
-bar:$?
bagiannya tidak akan dicampur selama tag string berada di bawah 507 byte.Jika Anda perlu mengambil output dari
bar
, itu menjadi sulit. Anda dapat menggabungkan teknik-teknik di atas dengan mengatur agarbar
tidak pernah mengandung garis yang terlihat seperti indikasi kode keluar, tetapi ia mendapatkan fiddly.Dan, tentu saja, ada opsi sederhana menggunakan file sementara untuk menyimpan status. Sederhana, tapi tidak yang sederhana dalam produksi:
/tmp
adalah satu-satunya tempat di mana skrip pasti dapat menulis file. Gunakanmktemp
, yang bukan POSIX tetapi tersedia di semua unit serius saat ini.sumber
Mulai dari pipa:
Berikut ini adalah solusi umum menggunakan hanya shell POSIX dan tidak ada file sementara:
$error_statuses
berisi kode status dari setiap proses yang gagal, dalam urutan acak, dengan indeks untuk mengetahui perintah mana yang dipancarkan setiap status.Catat kutipan di
$error_statuses
dalam tes saya; tanpa merekagrep
tidak dapat dibedakan karena baris baru dipaksa ke ruang.sumber
Jadi saya ingin menyumbangkan jawaban seperti lesmana, tetapi saya pikir jawaban saya mungkin sedikit lebih sederhana dan sedikit lebih menguntungkan dari solusi Bourne-shell:
Saya pikir ini paling baik dijelaskan dari dalam ke luar - command1 akan mengeksekusi dan mencetak output regulernya di stdout (file descriptor 1), kemudian setelah selesai, printf akan mengeksekusi dan mencetak kode keluar command1 pada stdout, tetapi stdout diarahkan ke deskriptor file 3.
Ketika command1 sedang berjalan, stdout-nya sedang dipipakan ke command2 (output printf tidak pernah membuatnya ke command2 karena kita mengirimkannya ke file descriptor 3, bukan 1, yang dibaca oleh pipa). Kemudian kita mengarahkan output command2 ke file descriptor 4, sehingga ia juga tetap keluar dari file descriptor 1 - karena kita ingin file descriptor 1 gratis sebentar kemudian, karena kita akan membawa output printf pada file deskriptor 3 kembali ke file descriptor 1 - karena itulah substitusi perintah (backticks), akan menangkap dan itulah yang akan ditempatkan ke dalam variabel.
Bit terakhir sihir adalah yang pertama
exec 4>&1
kita lakukan sebagai perintah terpisah - itu membuka file descriptor 4 sebagai salinan stdout shell eksternal. Substitusi perintah akan menangkap apa pun yang tertulis pada standar keluar dari perspektif perintah di dalamnya - tetapi, karena output command2 akan mengajukan deskriptor 4 sejauh menyangkut substitusi perintah, substitusi perintah tidak menangkapnya - namun, setelah "keluar" dari substitusi perintah, itu secara efektif masih pergi ke file deskriptor keseluruhan skrip 1.(Itu
exec 4>&1
harus menjadi perintah terpisah karena banyak shell umum tidak suka ketika Anda mencoba menulis ke deskriptor file di dalam substitusi perintah, yang dibuka di perintah "eksternal" yang menggunakan substitusi. Jadi ini adalah cara portabel paling sederhana untuk melakukannya.)Anda dapat melihatnya dengan cara yang kurang teknis dan lebih lucu, seolah-olah output dari perintah saling melompati satu sama lain: command1 pipa ke command2, maka output printf melompati perintah 2 sehingga perintah2 tidak menangkapnya, dan kemudian output perintah 2 melompati dan keluar dari substitusi perintah sama seperti printf mendarat tepat pada waktunya untuk ditangkap oleh substitusi sehingga berakhir di variabel, dan output command2 berjalan dengan cara yang menyenangkan ditulis ke output standar, sama seperti dalam pipa normal.
Juga, seperti yang saya mengerti,
$?
masih akan berisi kode kembali dari perintah kedua di dalam pipa, karena penugasan variabel, penggantian perintah, dan perintah majemuk semuanya secara efektif transparan ke kode pengembalian dari perintah di dalamnya, sehingga status pengembalian dari command2 harus disebarkan - ini, dan tidak harus mendefinisikan fungsi tambahan, itulah mengapa saya pikir ini mungkin solusi yang agak lebih baik daripada yang diusulkan oleh lesmana.Menurut peringatan lesmana, ada kemungkinan bahwa command1 pada akhirnya akan menggunakan deskriptor file 3 atau 4, jadi agar lebih kuat, Anda akan melakukan:
Perhatikan bahwa saya menggunakan perintah majemuk dalam contoh saya, tetapi subkulit (menggunakan
( )
alih-alih{ }
juga akan berfungsi, meskipun mungkin kurang efisien.)Perintah mewarisi deskriptor file dari proses yang meluncurkannya, sehingga seluruh baris kedua akan mewarisi deskriptor file empat, dan perintah majemuk diikuti oleh
3>&1
akan mewarisi deskriptor file tiga. Jadi4>&-
memastikan bahwa perintah inner compound tidak akan mewarisi file descriptor empat, dan3>&-
tidak akan mewarisi file deskriptor tiga, jadi command1 mendapat lingkungan yang lebih bersih dan lebih standar. Anda juga bisa memindahkan bagian dalam4>&-
ke sebelah3>&-
, tapi saya pikir mengapa tidak membatasi ruang lingkupnya sebanyak mungkin.Saya tidak yakin seberapa sering hal-hal menggunakan deskriptor file tiga dan empat secara langsung - Saya pikir sebagian besar program waktu menggunakan syscalls yang mengembalikan deskriptor file yang tidak digunakan saat ini, tetapi kadang-kadang kode menulis ke deskriptor file 3 secara langsung, saya tebak (saya bisa membayangkan sebuah program memeriksa deskriptor file untuk melihat apakah itu terbuka, dan menggunakannya jika ada, atau berperilaku berbeda sesuai jika tidak). Jadi yang terakhir mungkin terbaik untuk diingat dan digunakan untuk kasus-kasus tujuan umum.
sumber
-bash: 3: Bad file descriptor
.Jika Anda menginstal paket moreutils, Anda dapat menggunakan utilitas mispipe yang melakukan persis seperti yang Anda minta.
sumber
solusi lesmana di atas juga dapat dilakukan tanpa overhead memulai subproses bersarang dengan menggunakan
{ .. }
sebagai gantinya (mengingat bahwa bentuk perintah yang dikelompokkan ini selalu harus diakhiri dengan titik koma). Sesuatu seperti ini:Saya telah memeriksa konstruk ini dengan versi dash 0.5.5 dan versi bash 3.2.25 dan 4.2.42, jadi walaupun beberapa shell tidak mendukung
{ .. }
pengelompokan, itu masih sesuai dengan POSIX.sumber
set -o pipefail
di ksh atau sejumlahwait
perintah yang dipercikkan di salah satu. Saya pikir itu mungkin, sebagian setidaknya, menjadi masalah parsing untuk ksh, seolah-olah saya tetap menggunakan subshell maka itu berfungsi dengan baik, tetapi bahkan denganif
memilih varian subkulit untuk ksh tetapi meninggalkan perintah majemuk untuk orang lain, itu gagal .Ini portabel, yaitu berfungsi dengan shell yang sesuai dengan POSIX, tidak mengharuskan direktori saat ini dapat ditulis dan memungkinkan banyak skrip menggunakan trik yang sama untuk dijalankan secara bersamaan.
Sunting: ini adalah versi yang lebih kuat mengikuti komentar Gilles:
Sunting2: dan di sini ada varian yang sedikit lebih ringan mengikuti komentar dubiousjim:
sumber
(s=/tmp/.$$_$RANDOM;{foo;echo $?>$s;}|bar; exit $(cat $s;rm $s))
. @ Johan: Saya setuju itu lebih mudah dengan Bash tetapi dalam beberapa konteks, mengetahui bagaimana menghindari Bash sepadan.Mengikuti ini dimaksudkan sebagai tambahan untuk jawaban @ Patrik, jika Anda tidak dapat menggunakan salah satu solusi umum.
Jawaban ini mengasumsikan sebagai berikut:
$PIPESTATUS
atau tidakset -o pipefail
Sebelum:,
foo | bar | baz
namun ini hanya mengembalikan kode keluar dari perintah terakhir (baz
)Dicari:
$?
tidak boleh0
(benar), jika ada perintah di pipa gagalSetelah:
Dijelaskan:
mktemp
. Ini biasanya segera membuat file dalam/tmp
wait
diperlukan untukksh
, karenaksh
yang lain tidak menunggu semua pipa perintah untuk menyelesaikan. Namun harap dicatat bahwa ada efek samping yang tidak diinginkan jika ada tugas latar belakang, jadi saya berkomentar secara default. Jika menunggu tidak sakit, Anda dapat berkomentar.read
kembalifalse
, makatrue
tunjukkan kesalahanIni dapat digunakan sebagai pengganti plugin untuk satu perintah dan hanya perlu mengikuti:
/proc/fd/N
Bug:
Script ini memiliki bug jika
/tmp
kehabisan ruang. Jika Anda memerlukan perlindungan terhadap kasing buatan ini, Anda juga dapat melakukannya sebagai berikut, namun ini memiliki kekurangan, bahwa jumlah0
in000
tergantung pada jumlah perintah dalam pipa, sehingga sedikit lebih rumit:Catatan portablility:
ksh
dan shell serupa yang hanya menunggu perintah pipa terakhir perluwait
uncommentedContoh terakhir menggunakan
printf "%1s" "$?"
bukanecho -n "$?"
karena ini lebih portabel. Tidak semua platform mengartikan-n
dengan benar.printf "$?"
akan melakukannya juga, namunprintf "%1s"
menangkap beberapa kasus sudut jika Anda menjalankan skrip pada beberapa platform yang benar-benar rusak. (Baca: jika Anda kebetulan memprogram masukparanoia_mode=extreme
.)FD 8 dan FD 9 dapat lebih tinggi pada platform yang mendukung banyak digit. SETELAH shell konforman POSIX hanya perlu mendukung satu digit.
Diuji dengan Debian 8.2
sh
,bash
,ksh
,ash
,sash
dan bahkancsh
sumber
Dengan sedikit tindakan pencegahan, ini akan berhasil:
sumber
Blok 'jika' berikut ini akan berjalan hanya jika 'perintah' berhasil:
Secara khusus, Anda dapat menjalankan sesuatu seperti ini:
Yang akan menjalankan
haconf -makerw
dan menyimpan stdout dan stderr ke "$ haconf_out". Jika nilai yang dikembalikan darihaconf
benar, maka blok 'jika' akan dieksekusi dangrep
akan membaca "$ haconf_out", mencoba mencocokkannya dengan "Cluster sudah dapat ditulisi".Perhatikan bahwa pipa secara otomatis membersihkan diri mereka sendiri; dengan pengalihan Anda harus berhati-hati untuk menghapus "$ haconf_out" saat selesai.
Bukan sebagai elegan
pipefail
, tetapi alternatif yang sah jika fungsi ini tidak dalam jangkauan.sumber
sumber
(Dengan bash setidaknya) dikombinasikan dengan
set -e
satu dapat menggunakan subkulit untuk secara eksplisit meniru pipefail dan keluar pada kesalahan pipaJadi jika
foo
gagal karena beberapa alasan - sisa program tidak akan dieksekusi dan skrip keluar dengan kode kesalahan yang sesuai. (Ini mengasumsikan bahwafoo
mencetak kesalahannya sendiri, yang cukup untuk memahami alasan kegagalan)sumber
EDIT : Jawaban ini salah, tetapi menarik, jadi saya akan meninggalkannya untuk referensi di masa mendatang.
Menambahkan
!
ke perintah membalikkan kode kembali.http://tldp.org/LDP/abs/html/exit-status.html
sumber
ls
, bukan membalikkan kode keluarbogus_command