bash: bagaimana menyebarkan kesalahan dalam proses substitusi?

19

Saya ingin skrip shell saya gagal setiap kali perintah dieksekusi dengan mereka gagal.

Biasanya saya melakukannya dengan:

set -e
set -o pipefail

(biasanya saya tambahkan set -ujuga)

Masalahnya adalah bahwa tidak ada yang di atas bekerja dengan substitusi proses. Kode ini mencetak "ok" dan keluar dengan kode pengembalian = 0, sementara saya ingin gagal:

#!/bin/bash -e
set -o pipefail
cat <(false) <(echo ok)

Apakah ada yang setara dengan "pipefail" tetapi untuk substitusi proses? Adakah cara lain untuk meneruskan ke perintah output dari perintah seperti itu adalah file, tetapi meningkatkan kesalahan setiap kali salah satu program gagal?

Solusi orang miskin akan mendeteksi jika perintah-perintah itu menulis ke stderr (tetapi beberapa perintah menulis ke stderr dalam skenario sukses).

Solusi lain yang lebih sesuai dengan posix adalah menggunakan pipa bernama, tapi saya perlu menjalankan perintah-perintah-yang-menggunakan-proses sebagai oneliners yang dibangun dengan cepat dari kode yang dikompilasi, dan membuat pipa bernama akan mempersulit hal-hal (perintah tambahan, menjebak kesalahan untuk menghapusnya, dll.)

juanleon
sumber
Anda tidak perlu menjebak kesalahan untuk menghapus pipa bernama. Sebenarnya, itu bukan cara yang baik sama sekali. Pertimbangkan mkfifo pipe; { rm pipe; cat <file; } >pipe. Perintah itu akan digantung sampai pembaca membuka pipekarena itu adalah shell yang melakukan open()dan begitu begitu ada pembaca pada pipetautan fs untuk pipeis rm'd dan kemudian catsalinan infile keluar ke deskriptor shell untuk pipa itu. : <( ! : || kill -2 "$$")
Lagi
Terima kasih untuk tipe pada menghapus pipa bernama. Sayangnya, $$substitusi tidak berfungsi untuk saya, karena substitusi perintah tidak dilakukan karena perintah yang menggunakan proses substitusi dilakukan di dalam pipeline perintah yang dihasilkan dari kode "non shell" (python). Mungkin saya harus membuat subproses dengan python dan mengirimkannya secara terprogram.
juanleon
Jadi gunakan kill -2 0.
mikeserv
Sayangnya "kill -2 0" akan membunuh (signal) python. Menulis penangan sinyal untuk melakukan logika bisnis dalam aplikasi multithread bukan sesuatu yang saya harapkan :-)
juanleon
Jika Anda tidak ingin menangani sinyal maka mengapa Anda mencoba menerimanya? Lagi pula, saya berharap pipa bernama akan menjadi solusi paling sederhana pada akhirnya, jika hanya karena melakukannya dengan benar memerlukan menyusun kerangka kerja Anda sendiri dan mengatur dispatcher Anda sendiri. Atasi rintangan itu dan semuanya menyatu.
mikeserv

Jawaban:

6

Anda hanya bisa mengatasi masalah itu dengan itu misalnya:

cat <(false || kill $$) <(echo ok)
other_command

Subshell dari skrip adalah SIGTERMd sebelum perintah kedua dapat dieksekusi ( other_command). The echo okperintah dijalankan "kadang-kadang": Masalahnya adalah bahwa proses substitusi asynchronous. Tidak ada jaminan bahwa kill $$perintah dijalankan sebelum atau setelah itu echo okperintah. Ini masalah penjadwalan sistem operasi.

Pertimbangkan skrip bash seperti ini:

#!/bin/bash
set -e
set -o pipefail
cat <(echo pre) <(false || kill $$) <(echo post)
echo "you will never see this"

Output dari skrip itu bisa:

$ ./script
Terminated
$ echo $?
143           # it's 128 + 15 (signal number of SIGTERM)

Atau:

$ ./script
Terminated
$ pre
post

$ echo $?
143

Anda dapat mencobanya dan setelah beberapa kali mencoba, Anda akan melihat dua pesanan yang berbeda di output. Dalam yang pertama skrip dihentikan sebelum dua echoperintah lainnya bisa menulis ke deskriptor file. Dalam yang kedua perintah falseatau killmungkin dijadwalkan setelah echoperintah.

Atau untuk lebih tepatnya: Sistem panggilan signal()dari killutillity yang mengirim para SIGTERMsinyal ke proses kerang dijadwalkan (atau disampaikan) atau lebih lambat dari gema write()syscalls.

Namun, skrip berhenti dan kode keluar bukan 0. Karenanya harus menyelesaikan masalah Anda.

Solusi lain , tentu saja, menggunakan pipa bernama untuk ini. Tapi, itu tergantung pada skrip Anda seberapa kompleks untuk mengimplementasikan pipa bernama atau solusi di atas.

Referensi:

kekacauan
sumber
2

Sebagai catatan, dan bahkan jika jawaban dan komentarnya bagus dan bermanfaat, saya akhirnya menerapkan sesuatu yang sedikit berbeda (saya memiliki beberapa batasan tentang menerima sinyal dalam proses induk yang tidak saya sebutkan dalam pertanyaan)

Pada dasarnya, saya akhirnya melakukan sesuatu seperti ini:

command <(subcomand 2>error_file && rm error_file) <(....) ...

Lalu saya periksa file kesalahan. Jika ada, saya tahu sub-perintah apa yang gagal (dan isi dari error_file dapat bermanfaat). Lebih bertele-tele dan meretas yang awalnya saya inginkan, tetapi kurang rumit daripada membuat pipa bernama dalam perintah bash satu-liner.

juanleon
sumber
Apa pun yang bekerja untuk Anda biasanya merupakan cara terbaik. Terima kasih terutama untuk datang kembali dan menjawab - selfie adalah favorit saya.
mikeserv
2

Contoh ini menunjukkan cara menggunakan killbersama trap.

#! /bin/bash
failure ()
{
  echo 'sub process failed' >&2
  exit 1
}
trap failure SIGUSR1
cat < <( false || kill -SIGUSR1 $$ )

Tetapi killtidak dapat meneruskan kode kembali dari sub proses Anda ke proses induk.

mencari
sumber
Saya suka variasi ini pada jawaban yang diterima
lihat
1

Dengan cara yang sama seperti Anda akan mengimplementasikan $PIPESTATUS/ $pipestatusdengan shell POSIX yang tidak mendukungnya , Anda dapat memperoleh status keluar dari perintah dengan mengirimkannya melalui pipa:

unset -v false_status echo_status
{ code=$(
    exec 3>&1 >&4 4>&-
    cat 3>&- <(false 3>&-; echo >&3 "false_status=$?") \
             <(echo ok 3>&-; echo >&3 "echo_status=$?")
);} 4>&1
cat_status=$?
eval "$code"
printf '%s_code=%d\n' cat   "$cat_status" \
                      false "$false_status" \
                      echo  "$echo_status"

Pemberian yang mana:

ok
cat_code=0
false_code=1
echo_code=0

Atau Anda bisa menggunakan pipefaildan mengimplementasikan proses substitusi dengan tangan seperti yang Anda lakukan dengan shell yang tidak mendukungnya:

set -o pipefail
{
  false <&5 5<&- | {
    echo OK <&5 5<&- | {
      cat /dev/fd/3 /dev/fd/4
    } 4<&0 <&5 5<&-
  } 3<&0
} 5<&0
Stéphane Chazelas
sumber