Saya ingin menjalankan perintah yang berjalan lama di Bash, dan keduanya menangkap status keluarnya, dan mem - tee outputnya.
Jadi saya melakukan ini:
command | tee out.txt
ST=$?
Masalahnya adalah bahwa variabel ST menangkap status keluar tee
dan bukan dari perintah. Bagaimana saya bisa memecahkan masalah ini?
Perhatikan bahwa perintah sudah lama berjalan dan mengarahkan output ke file untuk melihatnya nanti bukan solusi yang baik untuk saya.
bash
shell
error-handling
pipe
flybywire
sumber
sumber
Jawaban:
Ada variabel Bash internal yang disebut
$PIPESTATUS
; ini adalah larik yang menyimpan status keluar dari setiap perintah di pipeline foreground perintah terakhir Anda.Atau alternatif lain yang juga bekerja dengan shell lain (seperti zsh) adalah dengan mengaktifkan pipefail:
Opsi pertama tidak berfungsi
zsh
karena sintaks yang sedikit berbeda.sumber
exit ${PIPESTATUS[0]}
.menggunakan bash
set -o pipefail
sangat membantusumber
( set -o pipefail; command | tee out.txt ); ST=$?
set -o pipefail
dan kemudian melakukan perintah, dan segera setelah itu lakukanset +o pipefail
untuk membatalkan pilihan.-o pipefail
dia akan tahu jika pipa gagal, tetapi jika 'perintah' dan 'tee' gagal, dia akan menerima kode keluar dari 'tee'.Solusi bodoh: Menghubungkan mereka melalui pipa bernama (mkfifo). Maka perintahnya bisa dijalankan kedua.
sumber
mkfifo
dan mungkin memintamknod -p
jika saya ingat benar.mkfifo
ataumknod -p
: dalam kasus saya perintah yang tepat untuk membuat file pipa adalahmknod FILE_NAME p
.Ada array yang memberi Anda status keluar dari setiap perintah dalam pipa.
sumber
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
.Inilah solusi saya:
Lihat jawaban saya untuk pertanyaan yang sama di unix.stackexchange.com untuk penjelasan terperinci dan alternatif tanpa subkulit dan beberapa peringatan.
sumber
Dengan menggabungkan
PIPESTATUS[0]
dan hasil mengeksekusiexit
perintah dalam subkulit, Anda dapat langsung mengakses nilai balik dari perintah awal Anda:command | tee ; ( exit ${PIPESTATUS[0]} )
Ini sebuah contoh:
akan memberimu:
return value: 1
sumber
VALUE=$(might_fail | piping)
yang tidak akan menetapkan PIPESTATUS di shell master tetapi akan mengatur tingkat kesalahannya. Dengan menggunakan:VALUE=$(might_fail | piping; exit ${PIPESTATUS[0]})
Saya mendapatkan keinginan yang saya inginkan.command_might_fail | grep -v "line_pattern_to_exclude" || exit ${PIPESTATUS[0]}
dalam kasus bukan tee tetapi grep filteringJadi 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 menjalankan dan mencetak output regulernya di stdout (file descriptor 1), kemudian setelah selesai, printf akan mengeksekusi dan mencetak kode keluar icommand1 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 mengirimnya 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 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 begitu mendapat "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 "eksternal" perintah yang menggunakan substitusi. 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 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 gabungan 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 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
Di Ubuntu dan Debian, Anda bisa
apt-get install moreutils
. Ini berisi utilitas yang disebutmispipe
yang mengembalikan status keluar dari perintah pertama di dalam pipa.sumber
Tidak seperti @ cODAR jawaban ini mengembalikan kode keluar asli dari perintah pertama dan tidak hanya 0 untuk sukses dan 127 untuk kegagalan. Tapi seperti yang ditunjukkan @Chaoran, Anda bisa menelepon
${PIPESTATUS[0]}
. Namun penting bahwa semua dimasukkan ke dalam tanda kurung.sumber
Di luar bash, Anda dapat melakukan:
Ini berguna misalnya dalam skrip ninja di mana shell diharapkan berada
/bin/sh
.sumber
PIPESTATUS [@] harus disalin ke array segera setelah perintah pipa kembali. Setiap pembacaan PIPESTATUS [@] akan menghapus konten. Salin ke array lain jika Anda berencana memeriksa status semua perintah pipa. "$?" bernilai sama dengan elemen terakhir dari "$ {PIPESTATUS [@]}", dan membacanya sepertinya menghancurkan "$ {PIPESTATUS [@]}", tapi saya belum benar-benar memverifikasi ini.
Ini tidak akan berfungsi jika pipa berada dalam sub-shell. Untuk solusi masalah itu,
lihat bash pipestatus di perintah backticked?
sumber
Cara paling sederhana untuk melakukan ini di bash polos adalah dengan menggunakan proses substitusi alih-alih pipa. Ada beberapa perbedaan, tetapi mereka mungkin tidak terlalu penting untuk kasus penggunaan Anda:
pipefail
pilihan danPIPESTATUS
variabel tidak relevan dengan substitusi proses.Dengan penggantian proses, bash baru saja memulai proses dan lupa tentangnya, itu bahkan tidak terlihat
jobs
.Menyebutkan perbedaan di samping,
consumer < <(producer)
danproducer | consumer
pada dasarnya setara.Jika Anda ingin membalik yang mana adalah proses "utama", Anda cukup membalik perintah dan arah substitusi ke
producer > >(consumer)
. Dalam kasus Anda:Contoh:
Seperti yang saya katakan, ada perbedaan dari ekspresi pipa. Proses ini mungkin tidak pernah berhenti berjalan, kecuali jika sensitif terhadap penutupan pipa. Secara khusus, mungkin terus menulis hal-hal untuk stdout Anda, yang mungkin membingungkan.
sumber
Solusi shell murni:
Dan sekarang dengan yang kedua
cat
digantikan olehfalse
:Harap perhatikan bahwa kucing pertama juga gagal, karena kucing itu sudah ditutup. Urutan perintah gagal dalam log sudah benar dalam contoh ini, tetapi jangan bergantung padanya.
Metode ini memungkinkan untuk menangkap stdout dan stderr untuk masing-masing perintah sehingga Anda dapat membuangnya juga ke dalam file log jika terjadi kesalahan, atau hapus saja jika tidak ada kesalahan (seperti output dari dd).
sumber
Berdasarkan jawaban @ brian-s-wilson; fungsi bash helper ini:
digunakan demikian:
1: get_bad_things harus berhasil, tetapi seharusnya tidak menghasilkan output; tapi kami ingin melihat output yang dihasilkannya
2: semua pipa harus berhasil
sumber
Terkadang lebih mudah dan jelas untuk menggunakan perintah eksternal, daripada menggali detail bash. pipeline , dari execline bahasa scripting proses minimal , keluar dengan kode kembali dari perintah kedua *, seperti halnya
sh
pipeline, tetapi tidak sepertish
itu, itu memungkinkan membalikkan arah pipa, sehingga kita dapat menangkap kode kembali dari produser proses (di bawah ini semua ada dish
baris perintah, tetapi denganexecline
diinstal):Menggunakan
pipeline
memiliki perbedaan yang sama dengan pipa bash asli sebagai pengganti proses bash yang digunakan dalam jawaban # 43972501 .* Sebenarnya
pipeline
tidak keluar sama sekali kecuali ada kesalahan. Ini dieksekusi ke dalam perintah kedua, jadi itu adalah perintah kedua yang melakukan pengembalian.sumber