it-swarm-id.com

Dapatkan status keluar dari proses yang disalurkan ke yang lain

Saya memiliki dua proses foo dan bar, terhubung dengan sebuah pipa:

$ foo | bar

bar selalu keluar 0; Saya tertarik pada kode keluar foo. Apakah ada cara untuk melakukannya?

314
Michael Mrozek

Jika Anda menggunakan bash, Anda dapat menggunakan variabel array PIPESTATUS 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 mulai dari 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 yang di atas dalam bash atau zsh dan Anda akan mendapatkan hasil yang sama; hanya satu dari retval_bash dan retval_zsh akan diatur. Yang lain akan kosong. Ini akan memungkinkan fungsi diakhiri dengan return $retval_bash $retval_zsh (perhatikan kurangnya kutipan!).

277
camh

Ada 3 cara umum untuk melakukan ini:

Pipefail

Cara pertama adalah mengatur opsi pipefail (ksh, zsh atau bash). Ini adalah yang paling sederhana dan apa yang dilakukannya pada dasarnya mengatur status keluar $? ke kode keluar dari program terakhir untuk keluar dari non-nol (atau nol jika semua berhasil keluar).

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

$ PIPESTATUS

Bash juga memiliki variabel array bernama $PIPESTATUS ($pipestatus dalam 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 dapat 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
253
Patrick

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 ingin status keluar dari someprog dan output dari filter.

Inilah solusi saya:

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

hasil dari konstruk ini adalah stdout dari filter sebagai stdout dari konstruk dan status keluar dari someprog sebagai status keluar dari konstruk.


konstruksi ini juga berfungsi dengan pengelompokan perintah sederhana {...} bukannya 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 someprog dan 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 someprog akan mewarisi deskriptor file terbuka 3 dan 4. Jika someprog menulis ke file deskriptor 3 maka itu akan menjadi status keluar. Status keluar sebenarnya akan diabaikan karena read hanya membaca sekali.

Jika Anda khawatir bahwa someprog Anda mungkin 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

exec 3>&- 4>&- sebelum someprog menutup deskriptor file sebelum mengeksekusi someprog jadi untuk someprog deskriptor file tersebut 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 $xs juga 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 pada #part2 dan pada gilirannya akan menjadi status keluar dari keseluruhan konstruk.
  4. Pipa dibuat dan perintah di sebelah kiri (#part5 dan #part6) dan kanan (filter >&4) dieksekusi. Output dari filter diarahkan ke file descriptor 4. Dalam #part1 deskriptor file 4 dialihkan ke stdout. Ini berarti bahwa output dari filter adalah stdout dari keseluruhan konstruk.
  5. Keluar dari status #part6 dicetak ke file deskriptor 3. Dalam #part3 file deskriptor 3 dialihkan ke #part2. Ini berarti bahwa status keluar dari #part6 akan menjadi status keluar terakhir untuk keseluruhan konstruk.
  6. someprog dijalankan. Status keluar diambil dalam #part5. Stdout diambil oleh pipa di #part4 dan diteruskan ke filter. Output dari filter pada gilirannya akan mencapai stdout seperti yang dijelaskan dalam #part4
58
lesmana

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

[[email protected] ~]# false | true
[[email protected] ~]# echo $?
0
[[email protected] ~]# set -o pipefail
[[email protected] ~]# false | true
[[email protected] ~]# echo $?
1
37
Chris

Apa yang saya lakukan jika memungkinkan adalah memberi makan kode keluar dari foo ke bar. Sebagai contoh, jika saya tahu bahwa foo tidak pernah menghasilkan garis dengan hanya digit, maka saya bisa menempelkan kode keluar:

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

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

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

Ini selalu dapat dilakukan jika ada beberapa cara untuk mendapatkan bar untuk bekerja pada semua kecuali baris terakhir, dan meneruskan baris terakhir sebagai kode keluarnya.

Jika bar adalah pipeline kompleks yang outputnya tidak Anda butuhkan, Anda dapat mem-bypassnya 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_codes biasanya foo:X bar:Y, tapi bisa juga bar:Y foo:X jika bar berhenti sebelum membaca semua inputnya atau jika Anda kurang beruntung. Saya pikir menulis ke pipa hingga 512 byte adalah atom pada semua unix, jadi foo:$? dan bar:$? bagian tidak akan di-intermix selama string tag di bawah 507 byte.

Jika Anda perlu mengambil output dari bar, itu menjadi sulit. Anda dapat menggabungkan teknik-teknik di atas dengan mengatur untuk output bar tidak pernah mengandung baris yang terlihat seperti indikasi kode keluar, tetapi itu 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, tetapi 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. Sering, /tmp adalah 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")

Mulai dari pipa:

foo | bar | baz

Berikut ini adalah solusi umum menggunakan hanya POSIX Shell 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

Perhatikan kutipan di sekitar $error_statuses dalam tes saya; tanpa mereka grep tidak dapat membedakan karena baris baru dipaksa untuk spasi.

17
Jander

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 menjalankan dan mencetak output regulernya di stdout (file descriptor 1), kemudian setelah selesai, printf akan mengeksekusi dan mencetak kode keluar command1 pada stdout-nya, tetapi stdout itu 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 descriptor 3 kembali ke file descriptor 1 - karena itulah substitusi perintah (backticks), akan menangkap dan itulah yang akan ditempatkan ke dalam variabel.

Bit terakhir dari sihir adalah yang pertama exec 4>&1 kami 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, secara efektif masih pergi ke deskriptor file keseluruhan skrip 1.

(The exec 4>&1 harus berupa perintah terpisah karena banyak shell umum tidak menyukainya ketika Anda mencoba menulis ke deskriptor file di dalam subtitusi perintah, yang dibuka di perintah "eksternal" yang menggunakan subtitusi. Jadi ini adalah cara portabel paling sederhana untuk melakukannya.)

Anda dapat melihatnya dengan cara yang kurang teknis dan lebih menyenangkan, 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 perintah2 harus disebarkan keluar - 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 ( ) dari pada { } 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. Sehingga 4>&- memastikan bahwa perintah compound compound tidak akan mewarisi deskriptor file empat, dan 3>&- tidak akan mewarisi deskriptor file tiga, jadi command1 mendapatkan 'cleaner', lingkungan yang lebih standar. Anda juga bisa menggerakkan bagian dalam 4>&- lanjut ke 3>&-, tapi saya pikir mengapa tidak membatasi cakupannya sebanyak mungkin.

Saya tidak yakin seberapa sering hal menggunakan deskriptor file tiga dan empat secara langsung - Saya pikir sebagian besar waktu program 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.

12
mtraceur

Jika Anda memiliki paket moreutils terinstal, Anda dapat menggunakan utilitas mispipe yang melakukan persis seperti yang Anda minta.

11
Emanuele Aina

solusi lesmana di atas juga dapat dilakukan tanpa overhead memulai subproses bersarang dengan menggunakan { .. } sebagai gantinya (mengingat bahwa bentuk perintah yang dikelompokkan ini harus selalu 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, masih memenuhi syarat POSIX.

7
pkeller

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 $PIPESTATUS atau set -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 bermain-main dengan deskriptor file 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 | baz, namun ini hanya mengembalikan kode keluar dari perintah terakhir (baz)

Ingin: $? tidak boleh 0 (true), jika ada perintah dalam 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 menambah FD 9 saja, jika ada kesalahan.
  • wait diperlukan untuk ksh, karena ksh lain tidak menunggu semua perintah pipa selesai. 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) read mengembalikan false, maka true menunjukkan 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
  • Itu juga platform agnostik dan tidak membutuhkan hal-hal seperti /proc/fd/N

Bug:

Skrip ini memiliki bug untuk berjaga-jaga /tmp kehabisan ruang. Jika Anda memerlukan perlindungan terhadap kasus buatan ini, Anda juga dapat melakukannya sebagai berikut, namun ini memiliki kekurangan, yaitu jumlah 0 di 000 tergantung pada jumlah perintah dalam pipa, jadi ini 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:

  • ksh dan cangkang serupa yang hanya menunggu perintah pipa terakhir memerlukan wait tidak dikomentari

  • Contoh terakhir menggunakan printf "%1s" "$?" dari pada echo -n "$?" karena ini lebih portabel. Tidak setiap platform mengartikan -n dengan 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 di paranoia_mode=extreme.)

  • FD 8 dan FD 9 dapat lebih tinggi pada platform yang mendukung banyak digit. AFAIR, Shell yang memenuhi POSIX hanya perlu mendukung satu digit.

  • Telah diuji dengan Debian 8.2 sh, bash, ksh, ash, sash dan bahkan csh

5
Tino

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 berjalan 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))
4
jlliagre

Dengan sedikit tindakan pencegahan, ini akan berhasil:

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

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 berjalan haconf -makerw dan simpan stdout dan stderr ke "$ haconf_out". Jika nilai yang dikembalikan dari haconf benar, maka blok 'jika' akan dieksekusi dan grep 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.

Tidak seanggun pipefail, tetapi alternatif yang sah jika fungsi ini tidak dapat dijangkau.

2
Rany Albeg Wein
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
$
1
C.G.

(Setidaknya dengan bash) dikombinasikan dengan set -e seseorang dapat menggunakan subkulit untuk secara eksplisit mengemulasi pipefail dan keluar dari kesalahan pipa

set -e
foo | bar
( exit ${PIPESTATUS[0]} )
rest of program

Jadi jika foo gagal karena suatu alasan - program lainnya tidak akan dieksekusi dan skrip keluar dengan kode kesalahan yang sesuai. (Ini mengasumsikan bahwa foo mencetak kesalahannya sendiri, yang cukup untuk memahami alasan kegagalan)

0
noonex