Bagaimana cara menangkap kode keluar / menangani kesalahan dengan benar saat menggunakan subtitusi proses?

13

Saya memiliki skrip yang mem-parsing nama file ke dalam array menggunakan metode berikut yang diambil dari T&J di SO :

unset ARGS
ARGID="1"
while IFS= read -r -d $'\0' FILE; do
    ARGS[ARGID++]="$FILE"
done < <(find "$@" -type f -name '*.txt' -print0)

Ini berfungsi dengan baik dan menangani semua jenis variasi nama file dengan sempurna. Namun, kadang-kadang, saya akan mengirimkan file yang tidak ada ke skrip, misalnya:

$ findscript.sh existingfolder nonexistingfolder
find: `nonexistingfile': No such file or directory
...

Dalam keadaan normal saya akan meminta skrip mengambil kode keluar dengan sesuatu seperti RET=$?dan menggunakannya untuk memutuskan bagaimana untuk melanjutkan. Ini sepertinya tidak bekerja dengan proses substitusi di atas.

Apa prosedur yang benar dalam kasus seperti ini? Bagaimana saya bisa mendapatkan kode pengembalian? Apakah ada cara lain yang lebih cocok untuk menentukan apakah ada yang salah dalam proses pengganti?

Glutanimate
sumber

Jawaban:

5

Anda dapat dengan mudah mendapatkan pengembalian dari setiap proses subshelled dengan mengulangi pengembaliannya di atas stdout. Hal yang sama berlaku untuk substitusi proses:

while IFS= read -r -d $'\0' FILE || 
    ! return=$FILE
do    ARGS[ARGID++]="$FILE"
done < <(find . -type f -print0; printf "$?")

Jika saya menjalankannya maka baris terakhir - (atau \0bagian dibatasi seperti kasusnya) akan menjadi findstatus pengembalian. readakan mengembalikan 1 ketika mendapat EOF - jadi satu-satunya waktu $returndiatur $FILEadalah untuk informasi terakhir yang dibaca.

Saya menggunakan printfuntuk menjaga dari menambahkan \newline tambahan - ini penting karena bahkan readdilakukan secara teratur - di mana Anda tidak membatasi pada \0NUL - akan mengembalikan selain 0 dalam kasus ketika data yang baru saja dibacanya tidak berakhir pada garis \ntepi. Jadi jika baris terakhir Anda tidak diakhiri dengan \newline, nilai terakhir dalam variabel read in Anda akan menjadi pengembalian Anda.

Menjalankan perintah di atas dan kemudian:

echo "$return"

KELUARAN

0

Dan jika saya mengubah bagian proses substitusi ...

...
done < <(! find . -type f -print0; printf "$?")
echo "$return"

KELUARAN

1

Demonstrasi yang lebih sederhana:

printf \\n%s list of lines printed to pipe |
while read v || ! echo "$v"
do :; done

KELUARAN

pipe

Dan pada kenyataannya, selama pengembalian yang Anda inginkan adalah hal terakhir yang Anda tulis di stdout dari dalam proses substitusi - atau proses subkunci dari mana Anda membaca dengan cara ini - maka $FILEakan selalu menjadi status pengembalian yang Anda inginkan saat itu. sudah lewat. Dan || ! return=...bagian itu tidak sepenuhnya diperlukan - itu digunakan untuk menunjukkan konsep saja.

mikeserv
sumber
5

Proses dalam substitusi proses tidak sinkron: shell meluncurkannya dan kemudian tidak memberikan cara untuk mendeteksi kapan mereka mati. Jadi Anda tidak akan dapat memperoleh status keluar.

Anda dapat menulis status keluar ke file, tetapi ini pada umumnya canggung karena Anda tidak dapat mengetahui kapan file tersebut ditulis. Di sini, file ditulis segera setelah akhir dari loop, jadi masuk akal untuk menunggu.

 < <(find …; echo $? >find.status.tmp; mv find.status.tmp find.status)
while ! [ -e find.status ]; do sleep 1; done
find_status=$(cat find.status; rm find.status)

Pendekatan lain adalah dengan menggunakan pipa bernama dan proses latar belakang (yang Anda bisa waituntuk).

mkfifo find_pipe
find  >find_pipe &
find_pid=$!
 <find_pipe
wait $find_pid
find_status=$?

Jika tidak ada pendekatan yang cocok, saya pikir Anda harus menuju ke bahasa yang lebih mampu, seperti Perl, Python atau Ruby.

Gilles 'SANGAT berhenti menjadi jahat'
sumber
Terima kasih atas jawaban ini. Metode yang Anda gambarkan berfungsi dengan baik tetapi saya harus mengakui bahwa mereka sedikit lebih rumit daripada yang saya perkirakan. Dalam kasus saya, saya menetap untuk loop sebelum yang ditunjukkan dalam pertanyaan yang mengulangi semua argumen dan mencetak kesalahan jika salah satu dari mereka bukan file atau folder. Meskipun ini tidak menangani jenis kesalahan lain yang dapat terjadi dalam proses yang disubstitusikan, itu cukup baik untuk kasus khusus ini. Jika saya membutuhkan metode penanganan kesalahan yang lebih canggih dalam situasi seperti ini, saya pasti akan kembali ke jawaban Anda.
Glutanimate
2

Gunakan coprocess . Menggunakan coprocbuiltin Anda dapat memulai subproses, membaca outputnya dan memeriksa status keluarnya:

coproc LS { ls existingdir; }
LS_PID_=$LS_PID
while IFS= read i; do echo "$i"; done <&"$LS"
wait "$LS_PID_"; echo $?

Jika direktori tidak ada, waitakan keluar dengan kode status bukan nol.

Saat ini perlu untuk menyalin PID ke variabel lain karena $LS_PIDakan tidak disetel sebelum waitdipanggil. Lihat Bash unsets * _PID variabel sebelum saya bisa menunggu di coproc untuk detailnya.

Feuermurmel
sumber
1
Saya ingin tahu kapan seseorang akan menggunakan <& "$ LS" vs read -u $ LS? - terima kasih
Brian Chrisman
1
@BrianChrisman Dalam contoh ini, mungkin tidak pernah. read -uharus bekerja dengan baik. Contoh itu dimaksudkan untuk menjadi generik dan menunjukkan bagaimana output dari proses dapat disalurkan ke perintah lain.
Feuermurmel
1

Salah satu pendekatan adalah:

status=0
token="WzNZY3CjqF3qkasn"    # some random string
while read line; do
    if [[ "$line" =~ $token:([[:digit:]]+) ]]; then
        status="${BASH_REMATCH[1]}"
    else
        echo "$line"
    fi
done < <(command; echo "$token:$?")
echo "Return code: $status"

Idenya adalah untuk menggemakan status keluar bersama dengan token acak setelah perintah selesai, dan kemudian menggunakan bash regular expressions untuk mencari dan mengekstrak status keluar. Token digunakan untuk membuat string unik untuk dicari di output.

Ini mungkin bukan cara terbaik untuk melakukannya dalam pengertian pemrograman umum, tetapi mungkin itu cara yang paling tidak menyakitkan untuk menanganinya dalam bash.

orev
sumber