Status keluar Bash digunakan dengan PIPE

10

Saya mencoba memahami bagaimana status keluar dikomunikasikan ketika pipa digunakan. Misalkan saya menggunakan whichuntuk mencari program yang tidak ada:

which lss
echo $?
1

Karena whichgagal mencari lsssaya mendapat status keluar dari 1. Ini baik-baik saja. Namun ketika saya coba yang berikut ini:

which lss | echo $?
0

Ini menunjukkan bahwa perintah terakhir yang dijalankan telah keluar secara normal. Satu-satunya cara saya dapat memahami ini adalah bahwa mungkin PIPE juga menghasilkan status keluar. Apakah ini cara yang tepat untuk memahaminya?

sshekhar1980
sumber
2
Semua komponen pipa berjalan pada waktu yang bersamaan . Dengan demikian, dalam which lss | echo $?, echodimulai sebelum whichtelah keluar; shell yang menghasilkan baris perintah untuk echotidak memiliki cara yang mungkin untuk mengetahui apa status keluar whichnantinya.
Charles Duffy
Mereka tidak berlari pada saat yang bersamaan. Seluruh tujuan pipa adalah untuk mengarahkan output dari satu perintah ke perintah lainnya. Perintah terakhir yang dieksekusi shell (yaitu yang terakhir pada pernyataan Anda) akan menjadi yang mengembalikan kode kembali.
Tim S.
3
@TIM. Tapi mereka berjalan pada saat yang sama, itulah cara Anda bisa mendapatkan bit pertama dari output sebelum perintah pertama selesai jika itu adalah perintah yang sangat lama berjalan.
Acak 832
Saya kira saya sedang memikirkan tentang awal eksekusi - ada tumpang tindih yang pasti, tetapi proses tidak dimulai bersama. Aku mengerti maksudmu, kesalahanku. (Pipa mengarahkan ulang output, bukan eksekusi ...) Saya merusak komentar saya, tapi mudah-mudahan Anda tahu apa yang saya maksud. :)
Tim S.
ya tapi mulai dari eksekusi tidak masalah, yang penting adalah akhirnya; perintah terakhir dimulai sebelum yang pertama berakhir
user371366

Jawaban:

14

Status keluar dari pipa adalah status keluar dari perintah terakhir dalam pipa (kecuali jika opsi shell pipefaildiatur dalam shell yang mendukungnya, dalam hal ini status keluar akan menjadi dari perintah terakhir dalam pipa yang keluar dengan status bukan nol).

Tidak mungkin untuk mengeluarkan status keluar dari pipa dari dalam pipa, karena tidak ada cara untuk mengetahui apa nilai itu sampai pipa benar-benar selesai dieksekusi.

Pipa

which lss | echo $?

tidak masuk akal karena echotidak membaca input standarnya (pipa digunakan untuk melewatkan data antara output dari satu perintah ke input yang berikutnya). Itu echojuga tidak akan mencetak status keluar dari pipa, tetapi status keluar dari perintah dijalankan segera sebelum pipa.

$ false
$ which hello | echo $?
1
which: hello: Command not found.

$ true
$ which hello | echo $?
0
which: hello: Command not found.

Ini adalah contoh yang lebih baik:

$ echo hello | read a
$ echo $?
0

$ echo nonexistent | { read filename && rm $filename; }
rm: nonexistent: No such file or directory
$ echo $?
1

Ini berarti bahwa saluran pipa juga dapat digunakan dengan if:

if gzip -dc file.gz | grep -q 'something'; then
  echo 'something was found'
fi
Kusalananda
sumber
10

Status keluar dari pipa adalah status keluar dari perintah sebelah kanan. Status keluar dari perintah sebelah kiri diabaikan.

(Catatan yang which lss | echo $?tidak menunjukkan ini. Anda akan menjalankan which lss | true; echo $?untuk menunjukkan ini. Dalam which lss | echo $?, echo $?melaporkan status perintah terakhir sebelum pipa ini.)

Alasan shell berperilaku seperti ini adalah bahwa ada skenario yang cukup umum di mana kesalahan di sisi kiri harus diabaikan. Jika sisi kanan keluar (atau lebih umum menutup input standarnya) ketika sisi kiri masih menulis, maka sisi kiri menerima sinyal SIGPIPE. Dalam hal ini, biasanya tidak ada yang salah: sisi kanan tidak peduli dengan data; jika peran sisi kiri semata-mata untuk menghasilkan data ini maka tidak masalah untuk menghentikannya.

Namun, jika sisi kiri mati karena alasan lain selain SIGPIPE, atau jika pekerjaan sisi kiri tidak hanya menghasilkan data pada output standar, maka kesalahan di sisi kiri adalah kesalahan asli yang harus dilaporkan.

Di sh polos, satu-satunya solusi adalah dengan menggunakan pipa bernama.

set -e
mkfifo p
command1 >p & pid1=$!
command2 <p
wait $pid1

Dalam ksh, bash, dan zsh, Anda dapat menginstruksikan shell untuk membuat pipa keluar dengan status bukan nol jika ada komponen pipa keluar dengan status bukan nol. Anda perlu mengatur pipefailopsi:

  • ksh: set -o pipefail
  • pesta: shopt -s pipefail
  • zsh: setopt pipefail(atau setopt pipe_fail)

Di mksh, bash dan zsh, Anda bisa mendapatkan status setiap komponen pipa menggunakan variabel PIPESTATUS(bash, mksh) atau pipestatus(zsh), yang merupakan larik yang berisi status semua perintah dalam pipa terakhir (generalisasi dari $?).

Gilles 'SANGAT berhenti menjadi jahat'
sumber
Terima kasih Gilles, saya tidak menyadari kompleksitas yang terlibat. Ini sangat memperjelas hal ini bagi saya.
sshekhar1980
4

Ya, PIPE menghasilkan status keluar; jika Anda ingin kode keluar dari perintah sebelum PIPE, Anda dapat menggunakan$PIPESTATUS

Menurut T&J Stack Overflow ini , untuk mencetak status keluar dari sebuah perintah saat Anda menggunakan pipa, Anda dapat melakukannya:

your_command | echo $PIPESTATUS

Atau atur pipefail dengan perintah set -o pipefail(untuk membatalkannya , lakukan set +o pipefail), dan gunakan $?sebagai ganti $PIPESTATUS.

your_command | echo $?

Perintah-perintah ini hanya berfungsi setelah Anda menjalankannya setidaknya satu kali; itu berarti PIPESTATUShanya berfungsi ketika Anda menjalankannya setelah perintah dengan pipa, seperti ini:

command_which_exit_10 | command_which_exit_1
echo "${PIPESTATUS[0]} ${PIPESTATUS[1]}"

Anda akan mendapatkan 10 untuk ${PIPESTATUS[0]}dan 1 untuk ${PIPESTATUS[1]}. Lihat U&L Q&A ini untuk informasi lebih lanjut.

jeremy.Snidaro
sumber
2
Meskipun PIPESTATUSberisi status keluar dari perintah di pipa terakhir, contoh pertama Anda tidak lebih masuk akal daripada somecmd | echo $?, yang ditangani Kusalananda dalam jawaban mereka. false; true | echo $PIPESTATUSmencetak 1untuk status keluar dari false.
ilkkachu
Suara positif untuk PIPESTATUS dan pipefiail, tetapi |exhobenar-benar membingungkan
eckes
0

Shell mengevaluasi baris perintah sebelum pipa dieksekusi. Begitu $?juga dengan nilai dari perintah terakhir yang dieksekusi sebelum pipeline. Apakah echoitu dimulai sebelum atau sesudah whichtidak relevan.

Sekarang jika pertanyaan Anda secara khusus tentang penyebaran status keluar, Anda dapat mempelajari perilaku default seperti ini:

% false | true
% echo $?
0

% true | false
% echo $?
1

Seperti yang Anda lihat, status keluar dari sebuah pipa adalah status keluar dari perintah terakhirnya.

Alexis
sumber