Dapatkan status keluar dari proses yang disalurkan ke yang lain


Jawaban:


256

Jika Anda menggunakan bash, Anda bisa menggunakan PIPESTATUSvariabel array untuk mendapatkan status keluar dari setiap elemen pipa.

$ false | true
$ echo "${PIPESTATUS[0]} ${PIPESTATUS[1]}"
1 0

Jika Anda menggunakan zsh, array mereka disebut pipestatus(hal penting!) Dan indeks array dimulai pada satu:

$ false | true
$ echo "${pipestatus[1]} ${pipestatus[2]}"
1 0

Untuk menggabungkan mereka dalam suatu fungsi dengan cara yang tidak kehilangan nilai:

$ false | true
$ retval_bash="${PIPESTATUS[0]}" retval_zsh="${pipestatus[1]}" retval_final=$?
$ echo $retval_bash $retval_zsh $retval_final
1 0

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!).


9
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).

$ false | true; echo $?
0
$ set -o pipefail
$ false | true; echo $?
1

$ PIPESTATUS

Bash juga memiliki variabel array bernama $PIPESTATUS( $pipestatusin zsh) yang berisi status keluar dari semua program dalam pipa terakhir.

$ true | true; echo "${PIPESTATUS[@]}"
0 0
$ false | true; echo "${PIPESTATUS[@]}"
1 0
$ false | true; echo "${PIPESTATUS[0]}"
1
$ true | false; echo "${PIPESTATUS[@]}"
0 1

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

$ OUTPUT="$(echo foo)"
$ STATUS_ECHO="$?"
$ printf '%s' "$OUTPUT" | grep -iq "bar"
$ STATUS_GREP="$?"
$ echo "$STATUS_ECHO $STATUS_GREP"
0 1

2
Menisik! Saya hanya akan memposting tentang PIPESTATUS.
slm

1
Untuk referensi ada beberapa teknik lain yang dibahas dalam pertanyaan SO ini: stackoverflow.com/questions/1221833/…
slm

1
@ 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.

Ini solusinya:

((((someprog; echo $? >&3) | filter >&4) 3>&1) | (read xs; exit $xs)) 4>&1

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

{ { { { someprog; echo $? >&3; } | filter >&4; } 3>&1; } | { read xs; exit $xs; } } 4>&1

Sayangnya 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 someprogdan filter:

someprog() {
  echo "line1"
  echo "line2"
  echo "line3"
  return 42
}

filter() {
  while read line; do
    echo "filtered $line"
  done
}

((((someprog; echo $? >&3) | filter >&4) 3>&1) | (read xs; exit $xs)) 4>&1

echo $?

Contoh output:

filtered line1
filtered line2
filtered line3
42

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.

(((((exec 3>&- 4>&-; someprog); echo $? >&3) | filter >&4) 3>&1) | (read xs; exit $xs)) 4>&1

The exec 3>&- 4>&-sebelum someprogmenutup file descriptor sebelum mengeksekusi someprogjadi untuk someprogfile yang mereka deskriptor sama sekali tidak ada.

Bisa juga ditulis seperti ini: someprog 3>&- 4>&-


Langkah demi langkah penjelasan konstruk:

( ( ( ( someprog;          #part6
        echo $? >&3        #part5
      ) | filter >&4       #part4
    ) 3>&1                 #part3
  ) | (read xs; exit $xs)  #part2
) 4>&1                     #part1

Dari bawah ke atas:

  1. 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.
  2. 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.
  3. 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.
  4. 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.
  5. 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.
  6. 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

1
Bagus. Untuk fungsi yang mungkin saya lakukan(read; exit $REPLY)
jthill

5
(exec 3>&- 4>&-; someprog)disederhanakan menjadi someprog 3>&- 4>&-.
Roger Pate

Metode ini juga berfungsi tanpa subkulit:{ { { { someprog 3>&- 4>&-; echo $? >&3; } | filter >&4; } 3>&1; } | { read xs; exit $xs; }; } 4>&1
josch

36

Meskipun tidak persis apa yang Anda minta, Anda bisa menggunakannya

#!/bin/bash -o pipefail

sehingga pipa Anda mengembalikan bukan nol kembali terakhir.

mungkin sedikit kurang coding

Edit: Contoh

[root@localhost ~]# false | true
[root@localhost ~]# echo $?
0
[root@localhost ~]# set -o pipefail
[root@localhost ~]# false | true
[root@localhost ~]# echo $?
1

9
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 -o pipefail /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:

{ foo; echo "$?"; } | awk '!/[^0-9]/ {exit($0)} {…}'

Atau jika saya tahu bahwa output dari footidak pernah berisi baris hanya dengan .:

{ foo; echo .; echo "$?"; } | awk '/^\.$/ {getline; exit($0)} {…}'

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.
foo_ret_file=$(mktemp -t)
{ foo; echo "$?" >"$foo_ret_file"; } | bar
bar_ret=$?
foo_ret=$(cat "$foo_ret_file"; rm -f "$foo_ret_file")

1
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:

exec 4>&1
error_statuses="`((foo || echo "0:$?" >&3) |
        (bar || echo "1:$?" >&3) | 
        (baz || echo "2:$?" >&3)) 3>&1 >&4`"
exec 4>&-

$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.


12

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:

exec 4>&1
exitstatus=`{ { command1 3>&-; printf $? 1>&3; } 4>&- | command2 1>&4; } 3>&1`
exec 4>&-

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


7

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:

{ { { { someprog; echo $? >&3; } | filter >&4; } 3>&1; } | stdintoexitstatus; } 4>&1

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.

(foo;echo $?>/tmp/_$$)|(bar;exit $(cat /tmp/_$$;rm /tmp/_$$))

Sunting: ini adalah versi yang lebih kuat mengikuti komentar Gilles:

(s=/tmp/.$$_$RANDOM;((foo;echo $?>$s)|(bar)); exit $(cat $s;rm $s))

Sunting2: dan di sini ada varian yang sedikit lebih ringan mengikuti komentar dubiousjim:

(s=/tmp/.$$_$RANDOM;{foo;echo $?>$s;}|bar; exit $(cat $s;rm $s))

3
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:

TMPRESULTS="`mktemp`"
{
rm -f "$TMPRESULTS"

{ foo; printf "%1s" "$?" >&9; } |
{ bar; printf "%1s" "$?" >&9; } |
{ baz; printf "%1s" "$?" >&9; }
#wait
read TMPRESULTS <&8
[ 000 = "$TMPRESULTS" ]
} 9>>"$TMPRESULTS" 8<"$TMPRESULTS"

Catatan portablility:

  • 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


3

Dengan sedikit tindakan pencegahan, ini akan berhasil:

foo-status=$(mktemp -t)
(foo; echo $? >$foo-status) | bar
foo_status=$(cat $foo-status)

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:

haconf_out=/path/to/some/temporary/file

if haconf -makerw > "$haconf_out" 2>&1; then
   grep -iq "Cluster already writable" "$haconf_out"
   # ...
fi

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.


1
Alternate example for @lesmana solution, possibly simplified.
Provides logging to file if desired.
=====
$ cat z.sh
TEE="cat"
#TEE="tee z.log"
#TEE="tee -a z.log"

exec 8>&- 9>&-
{
  {
    {
      { #BEGIN - add code below this line and before #END
./zz.sh
echo ${?} 1>&8  # use exactly 1x prior to #END
      #END
      } 2>&1 | ${TEE} 1>&9
    } 8>&1
  } | exit $(read; printf "${REPLY}")
} 9>&1

exit ${?}
$ cat zz.sh
echo "my script code..."
exit 42
$ ./z.sh; echo "status=${?}"
my script code...
status=42
$

0

(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)


-1

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

# =========================================================== #
# 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.
# =========================================================== #

1
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
Falmarri
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.