Itu
echo one; echo two > >(cat); echo three;
perintah memberikan output yang tak terduga.
Saya membaca ini: Bagaimana proses substitusi diimplementasikan dalam bash? dan banyak artikel lain tentang substitusi proses di internet, tetapi tidak mengerti mengapa ia berperilaku seperti ini.
Output yang diharapkan:
one
two
three
Output nyata:
prompt$ echo one; echo two > >(cat); echo three;
one
three
prompt$ two
Juga, kedua 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 berpikir, mereka harus sama? Karena, keduanya menghubungkan seq
output ke cat
input melalui pipa anonim - Wikipedia, Substitusi proses .
Pertanyaan: Mengapa berperilaku seperti ini? Di mana kesalahan saya? Jawaban komprehensif diinginkan (dengan penjelasan bagaimana bash
melakukannya di bawah tenda).
bash
process-substitution
MiniMax
sumber
sumber
Jawaban:
Ya,
bash
seperti diksh
( di mana fitur tersebut berasal), proses di dalam proses substitusi tidak menunggu (sebelum menjalankan perintah berikutnya dalam skrip).untuk
<(...)
satu, itu biasanya baik seperti di:shell akan menunggu
cmd1
dancmd1
biasanya akan menunggucmd2
berdasarkan pembacaan sampai akhir file pada pipa yang diganti, dan bahwa end-file biasanya terjadi ketikacmd2
mati. Itulah alasan yang sama mengapa beberapa kerang (tidakbash
) tidak perlu menunggu untukcmd2
masukcmd2 | cmd1
.Untuk
cmd1 >(cmd2)
, bagaimanapun, bahwa umumnya tidak terjadi, karena lebihcmd2
yang biasanya menunggucmd1
di sana sehingga umumnya akan keluar setelah.Itu sudah diperbaiki
zsh
yang menunggu dicmd2
sana (tetapi tidak jika Anda menuliskannya sebagaicmd1 > >(cmd2)
dancmd1
bukan bawaan, gunakan{cmd1} > >(cmd2)
sebagai gantinya yang didokumentasikan ).ksh
tidak menunggu secara default, tetapi memungkinkan Anda menunggu denganwait
builtin (itu juga membuat pid tersedia$!
, meskipun itu tidak membantu jika Anda melakukannyacmd1 >(cmd2) >(cmd3)
)rc
(dengancmd1 >{cmd2}
sintaks), sama sepertiksh
kecuali Anda bisa mendapatkan semua proses latar belakang dengan$apids
.es
(juga dengancmd1 >{cmd2}
) menunggucmd2
like inzsh
, dan juga menunggu redirectionscmd2
dalam<{cmd2}
proses.bash
memang membuat pid daricmd2
(atau lebih tepatnya dari subkulit seperti itu berjalancmd2
dalam proses anak dari subkulit itu meskipun itu adalah perintah terakhir di sana) tersedia di$!
, tetapi tidak membiarkan Anda menunggu untuk itu.Jika Anda harus menggunakan
bash
, Anda dapat mengatasi masalah dengan menggunakan perintah yang akan menunggu kedua perintah dengan:Itu membuat keduanya
cmd1
dancmd2
memiliki fd 3 terbuka untuk sebuah pipa.cat
akan menunggu akhir file di ujung yang lain, jadi biasanya hanya akan keluar ketika keduanyacmd1
dancmd2
sudah mati. Dan shell akan menunggucat
perintah itu. Anda dapat melihat bahwa 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 melatarbelakangi diri mereka sendiri asalkan mereka tidak menutup semua deskriptor file mereka seperti yang biasanya dilakukan daemon ).Perhatikan bahwa berkat proses subkulit sia-sia yang disebutkan di atas, ia bekerja bahkan jika
cmd2
menutup fd 3-nya (perintah biasanya tidak melakukan itu, tetapi beberapa sukasudo
ataussh
tidak). Versi masa depan padabash
akhirnya dapat melakukan optimasi di sana seperti di shell lain. Maka Anda akan membutuhkan sesuatu seperti:Untuk memastikan masih ada proses shell tambahan dengan fd 3 yang terbuka menunggu
sudo
perintah itu.Catatan yang
cat
tidak akan membaca apa pun (karena proses tidak menulis di fd 3 mereka). Itu hanya ada untuk sinkronisasi. Ini akan melakukan hanya saturead()
panggilan sistem yang akan kembali tanpa hasil di akhir.Anda sebenarnya dapat menghindari berjalan
cat
dengan menggunakan substitusi perintah untuk melakukan sinkronisasi pipa:Kali ini, itu shell bukan
cat
yang membaca dari pipa yang ujungnya terbuka pada fd 3 daricmd1
dancmd2
. Kami menggunakan tugas variabel sehingga status keluar daricmd1
tersedia di$?
.Atau Anda bisa melakukan proses substitusi dengan tangan, dan kemudian Anda bahkan bisa menggunakan sistem Anda
sh
karena itu akan menjadi sintaksis shell standar:meskipun perhatikan seperti disebutkan sebelumnya bahwa tidak semua
sh
implementasi akan menunggucmd1
setelahcmd2
selesai (meskipun itu lebih baik daripada sebaliknya). Waktu itu,$?
berisi status keluar daricmd2
; meskipunbash
danzsh
membuatcmd1
status keluar tersedia di${PIPESTATUS[0]}
dan$pipestatus[1]
masing - masing (lihat jugapipefail
opsi dalam beberapa shell sehingga$?
dapat melaporkan kegagalan komponen pipa selain yang terakhir)Catatan yang
yash
memiliki masalah serupa dengan fitur pengalihan prosesnya .cmd1 >(cmd2)
akan ditulis dicmd1 /dev/fd/3 3>(cmd2)
sana. Tetapicmd2
tidak menunggu dan Anda tidak dapat menggunakannyawait
untuk menunggu dan pidnya tidak tersedia dalam$!
variabel juga. Anda akan menggunakan pekerjaan yang sama untukbash
.sumber
echo one; { { echo two > >(cat); } 3>&1 >&4 4>&- | cat; } 4>&1; echo three;
, kemudian menyederhanakannyaecho one; echo two > >(cat) | cat; echo three;
dan menghasilkan nilai-nilai dalam urutan yang tepat juga. Apakah semua manipulasi deskriptor3>&1 >&4 4>&-
ini diperlukan? Juga, saya tidak mendapatkan ini>&4 4>&
- kami mengarahkanstdout
ke fd keempat, lalu menutup fd keempat, lalu menggunakannya lagi4>&1
. Mengapa perlu dan bagaimana cara kerjanya? Mungkin, saya harus membuat pertanyaan baru tentang topik ini?cmd1
dancmd2
, intinya dengan sedikit tarian dengan deskriptor file adalah mengembalikan yang asli dan hanya menggunakan pipa tambahan untuk menunggu, bukannya juga menyalurkan output dari perintah.4>&1
membuat file descriptor (fd) 4 untuk daftar perintah kawat gigi luar, dan membuatnya sama dengan stdout kawat gigi luar. Kawat gigi bagian dalam memiliki stdin / stdout / stderr secara otomatis diatur untuk menghubungkan ke kawat gigi luar. Namun,3>&1
membuat fd 3 terhubung ke stdin kawat gigi luar.>&4
membuat kawat gigi bagian dalam menghubungkan ke kawat gigi luar fd 4 (Yang kita buat sebelumnya).4>&-
menutup fd 4 dari kawat gigi bagian dalam (Karena stdout kawat gigi bagian dalam sudah terhubung ke kawat gigi luar fd 4).4>&1
dieksekusi terlebih dahulu, sebelum pengalihan lainnya, sehingga Anda tidak "menggunakan lagi4>&1
". Secara keseluruhan, kawat gigi bagian dalam mengirim data ke stdout-nya, yang ditimpa dengan apa pun fd 4 itu diberikan. Fd 4 yang diberikan kawat gigi bagian dalam, adalah kawat gigi luar 'fd 4, yang sama dengan stdout asli kawat gigi luar.4>5
berarti "4 pergi ke 5", tetapi benar-benar "fd 4 ditimpa dengan fd 5". Dan sebelum eksekusi, fd 0/1/2 terhubung secara otomatis (Seiring dengan fd dari shell luar), dan Anda dapat menimpa mereka seperti yang Anda inginkan. Setidaknya itulah interpretasi saya terhadap dokumentasi bash. Jika Anda memahami hal lain dari ini , lmk.Anda dapat menyalurkan perintah kedua ke perintah lain
cat
, yang akan menunggu sampai pipa inputnya tertutup. Ex:Singkat dan sederhana.
==========
Sesederhana tampaknya, banyak yang terjadi di belakang layar. Anda dapat mengabaikan sisa jawabannya jika Anda tidak tertarik dengan cara kerjanya.
Ketika Anda memiliki
echo two > >(cat); echo three
,>(cat)
dipotong oleh shell interaktif, dan berjalan secara independenecho two
. Dengan demikian,echo two
selesai, dan kemudianecho three
dieksekusi, tetapi sebelum>(cat)
selesai. Ketikabash
mendapatkan data dari>(cat)
saat itu tidak diharapkan (beberapa milidetik kemudian), itu memberi Anda situasi seperti cepat di mana Anda harus menekan baris baru untuk kembali ke terminal (Sama seperti jika pengguna lainmesg
menyunting Anda).Namun, mengingat
echo two > >(cat) | cat; echo three
, dua subkulit muncul (sesuai dokumentasi|
simbol).Satu subkulit bernama A adalah untuk
echo two > >(cat)
, dan satu subkulit bernama B adalah untukcat
. A secara otomatis terhubung ke B (stdout A adalah stdin B). Kemudian,echo two
dan>(cat)
mulai jalankan.>(cat)
stdout diatur ke stdout A, yang sama dengan stdin B. Setelahecho two
selesai, A keluar, menutup stdout-nya. Namun,>(cat)
masih memegang referensi ke stdin B.cat
Stdin kedua memegang stdin B, dan itucat
tidak akan keluar sampai ia melihat EOF. EOF hanya diberikan ketika tidak ada lagi file yang dibuka dalam mode tulis, jadi>(cat)
stdout memblokir yang keduacat
. B tetap menunggu pada detik itucat
. Karena memerah buffer dan keluar. Tidak ada yang memegang stdin B / detik lagi,echo two
keluar,>(cat)
akhirnya mendapat EOF, jadi>(cat)
cat
cat
membaca EOF (B sama sekali tidak membaca stdin, tidak peduli). EOF ini menyebabkancat
buffer kedua menyiram buffer, menutup stdout, dan keluar, dan kemudian B keluar karenacat
keluar dan B sedang menunggucat
.Peringatan dari ini adalah bahwa bash juga memunculkan subkulit untuk
>(cat)
! Karena ini, Anda akan melihatnyaecho two > >(sleep 5) | cat; echo three
masih akan menunggu 5 detik sebelum dieksekusi
echo three
, meskipunsleep 5
tidak memegang stdin B. Ini karena subkulit tersembunyi yang ditumbuhkan C>(sleep 5)
sedang menunggusleep
, dan C memegang stdin B. Anda bisa lihat caranyaecho two > >(exec sleep 5) | cat; echo three
Tidak akan menunggu, karena
sleep
tidak memegang stdin B, dan tidak ada subshell hantu C yang memegang stdin B (eksekutif akan memaksa tidur untuk menggantikan C, yang bertentangan dengan forking dan membuat C menunggusleep
). Terlepas dari peringatan ini,echo two > >(exec cat) | cat; echo three
masih akan menjalankan fungsi dengan benar, seperti dijelaskan sebelumnya.
sumber
A
tidak menunggucat
melahirkan di>(cat)
. Seperti yang saya sebutkan dalam jawaban saya, alasan mengapaecho two > >(sleep 5 &>/dev/null) | cat; echo three
outputthree
setelah 5 detik adalah karena versi saat inibash
limbah proses shell tambahan>(sleep 5)
yang menunggusleep
dan proses itu masih stdout pergi kepipe
yang mencegah yang keduacat
dari penghentian. Jika Anda menggantinya denganecho two > >(exec sleep 5 &>/dev/null) | cat; echo three
untuk menghilangkan proses tambahan itu, Anda akan menemukan bahwa itu mengembalikan langsung.echo two > >(sleep 5 &>/dev/null)
minimum mendapatkan subshell sendiri. Apakah ini merupakan detail implementasi yang tidak terdokumentasi yang menyebabkansleep 5
mendapatkan subkulitnya sendiri juga? Jika didokumentasikan maka itu akan menjadi cara yang sah untuk menyelesaikannya dengan karakter yang lebih sedikit (Kecuali jika ada loop ketat saya tidak berpikir ada yang akan melihat masalah kinerja dengan subkulit, atau kucing) `. Jika tidak didokumentasikan maka rip, hack yang bagus, tidak akan bekerja pada versi yang akan datang.$(...)
,<(...)
memang melibatkan subkulit, tetapi ksh93 atau zsh akan menjalankan perintah terakhir dalam subkulit itu dalam proses yang sama, bukanbash
itu sebabnya masih ada proses lain memegang pipa terbuka saatsleep
sedang menjalankan tidak memegang pipa terbuka. Versi masa depanbash
dapat menerapkan pengoptimalan yang serupa.exec
, itu berfungsi seperti yang diharapkan.