echo one; echo two > >(cat); echo three;
perintah memberikan keluaran yang tidak diharapkan.
Saya membaca ini:Bagaimana proses substitusi diimplementasikan di bash? dan banyak artikel lain tentang proses substitusi di internet, tetapi tidak mengerti mengapa ia berperilaku seperti ini.
Keluaran yang diharapkan:
one
two
three
Keluaran nyata:
prompt$ echo one; echo two > >(cat); echo three;
one
three
prompt$ two
Juga, dua perintah ini harus setara dari sudut pandang saya, tetapi tidak:
##### first command - the pipe is used.
prompt$ seq 1 5 | cat
1
2
3
4
5
##### second command - the process substitution and redirection are used.
prompt$ seq 1 5 > >(cat)
prompt$ 1
2
3
4
5
Mengapa saya pikir, mereka harus sama? Karena, keduanya menghubungkan seq
output ke cat
masukan melalui pipa anonim – Wikipedia, Substitusi proses.
Pertanyaan: Mengapa berperilaku seperti ini? Di mana kesalahan saya? Jawaban yang komprehensif diinginkan (dengan penjelasan tentang bagaimana bash
melakukannya di bawah tenda).
Jawaban yang Diterima:
Ya, di bash
seperti di ksh
(dari mana fitur tersebut berasal), proses di dalam proses substitusi tidak menunggu (sebelum menjalankan perintah berikutnya dalam skrip).
untuk <(...)
satu, itu biasanya baik-baik saja seperti di:
cmd1 <(cmd2)
shell akan menunggu cmd1
dan cmd1
biasanya akan menunggu cmd2
berdasarkan itu membaca sampai akhir file pada pipa yang diganti, dan akhir file itu biasanya terjadi ketika cmd2
mati. Itulah alasan yang sama beberapa shell (bukan bash
) jangan repot-repot menunggu cmd2
di cmd2 | cmd1
.
Untuk cmd1 >(cmd2)
, namun, umumnya tidak demikian, karena lebih dari cmd2
yang biasanya menunggu cmd1
di sana jadi biasanya akan keluar setelahnya.
Itu diperbaiki di zsh
yang menunggu cmd2
di sana (tetapi tidak jika Anda menulisnya sebagai cmd1 > >(cmd2)
dan cmd1
tidak terpasang, gunakan {cmd1} > >(cmd2)
sebagai gantinya seperti yang didokumentasikan).
ksh
tidak menunggu secara default, tetapi memungkinkan Anda menunggu dengan wait
builtin (itu juga membuat pid tersedia di $!
, meskipun itu tidak membantu jika Anda melakukan cmd1 >(cmd2) >(cmd3)
)
rc
(dengan cmd1 >{cmd2}
sintaks), sama seperti ksh
kecuali Anda bisa mendapatkan pid dari semua proses latar belakang dengan $apids
.
es
(juga dengan cmd1 >{cmd2}
) menunggu cmd2
seperti di zsh
, dan juga menunggu cmd2
di <{cmd2}
proses pengalihan.
bash
memang membuat pid dari cmd2
(atau lebih tepatnya subkulit seperti yang dijalankan cmd2
dalam proses anak dari subkulit itu meskipun itu adalah perintah terakhir di sana) tersedia di $!
, tetapi tidak membuat Anda menunggu.
Jika Anda harus menggunakan bash
, Anda dapat mengatasi masalah tersebut dengan menggunakan perintah yang akan menunggu kedua perintah dengan:
{ { cmd1 >(cmd2); } 3>&1 >&4 4>&- | cat; } 4>&1
Itu membuat keduanya cmd1
dan cmd2
memiliki fd 3 mereka terbuka ke pipa. cat
akan menunggu akhir file di ujung yang lain, jadi biasanya hanya akan keluar ketika keduanya cmd1
dan cmd2
telah mati. Dan shell akan menunggu cat
itu memerintah. Anda dapat melihatnya sebagai jaring untuk menangkap penghentian semua proses latar belakang (Anda dapat menggunakannya untuk hal-hal lain yang dimulai di latar belakang seperti dengan &
, coprocs, atau bahkan perintah yang memiliki latar belakang sendiri asalkan mereka tidak menutup semua deskriptor file mereka seperti yang biasanya dilakukan daemon).
Perhatikan bahwa berkat proses subkulit yang terbuang yang disebutkan di atas, ini berfungsi bahkan jika cmd2
menutup fd 3-nya (perintah biasanya tidak melakukan itu, tetapi beberapa seperti sudo
atau ssh
melakukan). Versi bash
yang akan datang akhirnya dapat melakukan optimasi di sana seperti di shell lainnya. Maka Anda memerlukan sesuatu seperti:
{ { cmd1 >(sudo cmd2; exit); } 3>&1 >&4 4>&- | cat; } 4>&1
Untuk memastikan masih ada proses shell ekstra dengan fd 3 terbuka menunggu sudo
itu perintah.
Perhatikan bahwa cat
tidak akan membaca apa pun (karena proses tidak menulis di fd 3). Itu hanya ada untuk sinkronisasi. Itu hanya akan melakukan satu read()
panggilan sistem yang akan kembali tanpa hasil.
Anda sebenarnya dapat menghindari menjalankan cat
dengan menggunakan substitusi perintah untuk melakukan sinkronisasi pipa:
{ unused=$( { cmd1 >(cmd2); } 3>&1 >&4 4>&-); } 4>&1
Kali ini cangkangnya bukan cat
yaitu membaca dari pipa yang ujung lainnya terbuka pada fd 3 dari cmd1
dan cmd2
. Kami menggunakan penugasan variabel sehingga status keluar dari cmd1
tersedia di $?
.
Atau Anda dapat melakukan proses substitusi dengan tangan, dan kemudian Anda bahkan dapat menggunakan sh
. sistem Anda karena itu akan menjadi sintaks shell standar:
{ cmd1 /dev/fd/3 3>&1 >&4 4>&- | cmd2 4>&-; } 4>&1
meskipun perhatikan seperti yang disebutkan sebelumnya bahwa tidak semua sh
implementasi akan menunggu cmd1
setelah cmd2
telah selesai (meskipun itu lebih baik daripada sebaliknya). Waktu itu, $?
berisi status keluar dari cmd2
; meskipun bash
dan zsh
buat cmd1
status keluar tersedia di ${PIPESTATUS[0]}
dan $pipestatus[1]
masing-masing (lihat juga pipefail
opsi dalam beberapa shell jadi $?
dapat melaporkan kegagalan komponen pipa selain yang terakhir)
Perhatikan bahwa yash
memiliki masalah serupa dengan proses pengalihan fitur. cmd1 >(cmd2)
akan ditulis cmd1 /dev/fd/3 3>(cmd2)
di sana. Tapi cmd2
tidak menunggu dan Anda tidak dapat menggunakan wait
untuk menunggunya juga dan pidnya tidak tersedia di $!
variabel baik. Anda akan menggunakan pekerjaan yang sama seperti untuk bash
.