Dapatkah seseorang memberikan beberapa contoh tentang cara menggunakan coproc
?
Jawaban yang Diterima:
co-proses adalah ksh
fitur (sudah ada di ksh88
). zsh
telah memiliki fitur dari awal (awal 90-an), sementara itu baru saja ditambahkan ke bash
di 4.0
(2009).
Namun, perilaku dan antarmuka berbeda secara signifikan antara 3 shell.
Idenya sama, meskipun:memungkinkan untuk memulai pekerjaan di latar belakang dan dapat mengirimkan input dan membaca outputnya tanpa harus menggunakan pipa bernama.
Itu dilakukan dengan pipa tanpa nama dengan sebagian besar shell dan socketpairs dengan versi terbaru dari ksh93 pada beberapa sistem.
Dalam a | cmd | b
, a
memasukkan data ke cmd
dan b
membaca outputnya. Menjalankan cmd
sebagai proses bersama memungkinkan shell menjadi a
dan b
.
ksh co-proses
Dalam ksh
, Anda memulai koproses sebagai:
cmd |&
Anda memasukkan data ke cmd
dengan melakukan hal-hal seperti:
echo test >&p
atau
print -p test
Dan baca cmd
keluaran dengan hal-hal seperti:
read var <&p
atau
read -p var
cmd
dimulai sebagai pekerjaan latar belakang, Anda dapat menggunakan fg
, bg
, kill
di atasnya dan rujuk dengan %job-number
atau melalui $!
.
Untuk menutup tulisan ujung pipa cmd
sedang membaca dari, Anda dapat melakukan:
exec 3>&p 3>&-
Dan untuk menutup ujung pembacaan pipa lainnya (yang satu cmd
sedang menulis ke):
exec 3<&p 3<&-
Anda tidak dapat memulai proses bersama kedua kecuali Anda terlebih dahulu menyimpan deskriptor file pipa ke beberapa fds lain. Misalnya:
tr a b |&
exec 3>&p 4<&p
tr b c |&
echo aaa >&3
echo bbb >&p
zsh co-proses
Dalam zsh
, co-proses hampir identik dengan yang ada di ksh
. Satu-satunya perbedaan nyata adalah zsh
co-proses dimulai dengan coproc
kata kunci.
coproc cmd
echo test >&p
read var <&p
print -p test
read -p var
Melakukan:
exec 3>&p
Catatan:Ini tidak memindahkan coproc
deskriptor file ke fd 3
(seperti di ksh
), tetapi menggandakannya. Jadi, tidak ada cara eksplisit untuk menutup pipa pengumpanan atau pembacaan, selain memulai lain coproc
.
Misalnya, untuk menutup ujung pengumpanan:
coproc tr a b
echo aaaa >&p # send some data
exec 4<&p # preserve the reading end on fd 4
coproc : # start a new short-lived coproc (runs the null command)
cat <&4 # read the output of the first coproc
Selain proses bersama berbasis pipa, zsh
(sejak 3.1.6-dev19, dirilis pada tahun 2000) memiliki konstruksi berbasis pseudo-tty seperti expect
. Untuk berinteraksi dengan sebagian besar program, proses bersama gaya ksh tidak akan berfungsi, karena program mulai buffering saat outputnya berupa pipa.
Berikut beberapa contohnya.
Mulai proses bersama x
:
zmodload zsh/zpty
zpty x cmd
(Di sini, cmd
adalah perintah sederhana. Tetapi Anda dapat melakukan hal-hal yang lebih menarik dengan eval
atau fungsi.)
Masukkan data proses bersama:
zpty -w x some data
Baca data proses bersama (dalam kasus yang paling sederhana):
zpty -r x var
Seperti expect
, ia dapat menunggu beberapa keluaran dari proses bersama yang cocok dengan pola yang diberikan.
proses bersama bash
Sintaks bash jauh lebih baru, dan dibangun di atas fitur baru yang baru saja ditambahkan ke ksh93, bash, dan zsh. Ini menyediakan sintaks untuk memungkinkan penanganan deskriptor file yang dialokasikan secara dinamis di atas 10.
bash
menawarkan dasar coproc
sintaks, dan diperpanjang satu.
Sintaks dasar
Sintaks dasar untuk memulai proses bersama terlihat seperti zsh
's:
coproc cmd
Dalam ksh
atau zsh
, pipa ke dan dari proses bersama diakses dengan >&p
dan <&p
.
Tapi di bash
, deskriptor file pipa dari proses bersama dan pipa lain ke proses bersama dikembalikan dalam $COPROC
array (masing-masing ${COPROC[0]}
dan ${COPROC[1]}
. Jadi…
Masukkan data ke proses bersama:
echo xxx >&"${COPROC[1]}"
Baca data dari proses bersama:
read var <&"${COPROC[0]}"
Dengan sintaks dasar, Anda hanya dapat memulai satu proses bersama pada satu waktu.
Sintaks yang diperluas
Dalam sintaks yang diperluas, Anda dapat nama proses bersama Anda (seperti di zsh
zpty co-proses):
coproc mycoproc { cmd; }
Perintah memiliki menjadi perintah majemuk. (Perhatikan bagaimana contoh di atas mengingatkan pada function f { ...; }
.)
Kali ini, deskriptor file ada di ${mycoproc[0]}
dan ${mycoproc[1]}
.
Anda dapat memulai lebih dari satu proses bersama dalam satu waktu—tetapi Anda melakukannya dapatkan peringatan saat Anda memulai proses bersama saat masih berjalan (bahkan dalam mode non-interaktif).
Anda dapat menutup deskriptor file saat menggunakan sintaks yang diperluas.
coproc tr { tr a b; }
echo aaa >&"${tr[1]}"
exec {tr[1]}>&-
cat <&"${tr[0]}"
Perhatikan bahwa menutup dengan cara itu tidak berfungsi di versi bash sebelum 4.3 di mana Anda harus menulisnya sebagai gantinya:
fd=${tr[1]}
exec {fd}>&-
Seperti pada ksh
dan zsh
, deskriptor file pipa tersebut ditandai sebagai close-on-exec.
Tapi di bash
, satu-satunya cara untuk meneruskannya ke perintah yang dieksekusi adalah dengan menduplikasinya ke fds ,
1
, atau 2
. Itu membatasi jumlah proses bersama yang dapat berinteraksi dengan Anda untuk satu perintah. (Lihat di bawah untuk contoh.)
proses yash dan pengalihan pipeline
yash
tidak memiliki fitur proses bersama, tetapi konsep yang sama dapat diimplementasikan dengan pipanya dan proses fitur pengalihan. yash
memiliki antarmuka ke pipe()
panggilan sistem, jadi hal semacam ini dapat dilakukan dengan relatif mudah dengan tangan di sana.
Anda akan memulai proses bersama dengan:
exec 5>>|4 3>(cmd >&5 4<&- 5>&-) 5>&-
Yang pertama membuat pipe(4,5)
(5 akhir penulisan, 4 akhir pembacaan), kemudian mengarahkan ulang fd 3 ke pipa ke proses yang berjalan dengan stdinnya di ujung lainnya, dan stdout menuju pipa yang dibuat sebelumnya. Kemudian kita menutup ujung tulisan pipa itu di induk yang tidak kita perlukan. Jadi sekarang di shell kita memiliki fd 3 terhubung ke stdin cmd dan fd 4 terhubung ke stdout cmd dengan pipa.
Perhatikan bahwa tanda close-on-exec tidak disetel pada deskriptor file tersebut.
Untuk memberi makan data:
echo data >&3 4<&-
Untuk membaca data:
read var <&4 3>&-
Dan Anda dapat menutup fds seperti biasa:
exec 3>&- 4<&-
Sekarang, mengapa mereka tidak begitu populer
hampir tidak ada manfaatnya menggunakan pipa bernama
Co-proses dapat dengan mudah diimplementasikan dengan pipa bernama standar. Saya tidak tahu kapan tepatnya pipa bernama diperkenalkan tetapi mungkin setelah ksh
datang dengan co-proses (mungkin pada pertengahan 80-an, ksh88 "dirilis" di 88, tapi saya percaya ksh
digunakan secara internal di AT&T beberapa tahun sebelumnya) yang akan menjelaskan alasannya.
cmd |&
echo data >&p
read var <&p
Dapat ditulis dengan:
mkfifo in out
cmd <in >out &
exec 3> in 4< out
echo data >&3
read var <&4
Berinteraksi dengan itu lebih mudah—terutama jika Anda perlu menjalankan lebih dari satu proses bersama. (Lihat contoh di bawah.)
Satu-satunya keuntungan menggunakan coproc
adalah Anda tidak perlu membersihkan pipa yang diberi nama tersebut setelah digunakan.
rawan kebuntuan
Shell menggunakan pipa dalam beberapa konstruksi:
- pipa cangkang:
cmd1 | cmd2
, - penggantian perintah:
$(cmd)
, - dan proses substitusi:
<(cmd)
,>(cmd)
.
Di dalamnya, data mengalir hanya satu arah antara proses yang berbeda.
Namun, dengan proses bersama dan pipa bernama, mudah menemui jalan buntu. Anda harus melacak perintah mana yang memiliki deskriptor file mana yang terbuka, untuk mencegahnya tetap terbuka dan menahan proses tetap hidup. Deadlock bisa jadi sulit untuk diselidiki, karena dapat terjadi secara non-deterministik; misalnya, hanya ketika data sebanyak untuk mengisi satu pipa dikirim.
berfungsi lebih buruk dari expect
untuk apa itu dirancang
Tujuan utama dari proses bersama adalah untuk menyediakan shell dengan cara untuk berinteraksi dengan perintah. Namun, itu tidak bekerja dengan baik.
Bentuk paling sederhana dari kebuntuan tersebut di atas adalah:
tr a b |&
echo a >&p
read var<&p
Karena outputnya tidak masuk ke terminal, tr
buffer outputnya. Jadi itu tidak akan menampilkan apa pun sampai ia melihat akhir file di stdin
, atau telah mengumpulkan buffer-penuh data ke output. Jadi di atas, setelah shell memiliki output an
(hanya 2 byte), read
akan memblokir tanpa batas karena tr
sedang menunggu shell untuk mengirimkan lebih banyak data.
Singkatnya, pipa tidak baik untuk berinteraksi dengan perintah. Co-proses hanya dapat digunakan untuk berinteraksi dengan perintah yang tidak menyangga outputnya, atau perintah yang dapat diberitahu untuk tidak menyangga outputnya; misalnya, dengan menggunakan stdbuf
dengan beberapa perintah pada sistem GNU atau FreeBSD terbaru.
Itu sebabnya expect
atau zpty
gunakan terminal semu sebagai gantinya. expect
adalah alat yang dirancang untuk berinteraksi dengan perintah, dan berfungsi dengan baik.
Penanganan deskriptor file rumit, dan sulit untuk diperbaiki
Co-proses dapat digunakan untuk melakukan beberapa pemipaan yang lebih kompleks daripada yang diizinkan oleh pipa shell sederhana.
bahwa jawaban Unix.SE lainnya memiliki contoh penggunaan coproc.
Berikut adalah contoh yang disederhanakan: Bayangkan Anda menginginkan fungsi yang mengumpankan salinan output perintah ke 3 perintah lain, lalu membuat output dari 3 perintah tersebut digabungkan.
Semua menggunakan pipa.
Misalnya:feed output dari printf '%sn' foo bar
ke tr a b
, sed 's/./&&/g'
, dan cut -b2-
untuk mendapatkan sesuatu seperti:
foo
bbr
ffoooo
bbaarr
oo
ar
Pertama, ini belum tentu jelas, tetapi ada kemungkinan kebuntuan di sana, dan itu akan mulai terjadi setelah hanya beberapa kilobyte data.
Kemudian, bergantung pada shell Anda, Anda akan menghadapi sejumlah masalah berbeda yang harus ditangani secara berbeda.
Terkait:Bagaimana wildcard * ditafsirkan sebagai perintah?
Misalnya, dengan zsh
, Anda akan melakukannya dengan:
f() (
coproc tr a b
exec {o1}<&p {i1}>&p
coproc sed 's/./&&/g' {i1}>&- {o1}<&-
exec {o2}<&p {i2}>&p
coproc cut -c2- {i1}>&- {o1}<&- {i2}>&- {o2}<&-
tee /dev/fd/$i1 /dev/fd/$i2 >&p {o1}<&- {o2}<&- &
exec cat /dev/fd/$o1 /dev/fd/$o2 - <&p {i1}>&- {i2}>&-
)
printf '%sn' foo bar | f
Di atas, fds co-proses memiliki flag close-on-exec yang disetel, tetapi tidak yang diduplikasi dari mereka (seperti dalam {o1}<&p
). Jadi, untuk menghindari kebuntuan, Anda harus memastikan kebuntuan itu tertutup dalam proses apa pun yang tidak membutuhkannya.
Demikian pula, kita harus menggunakan subkulit dan menggunakan exec cat
pada akhirnya, untuk memastikan tidak ada proses shell yang berbohong tentang menahan pipa tetap terbuka.
Dengan ksh
(di sini ksh93
), itu harus:
f() (
tr a b |&
exec {o1}<&p {i1}>&p
sed 's/./&&/g' |&
exec {o2}<&p {i2}>&p
cut -c2- |&
exec {o3}<&p {i3}>&p
eval 'tee "/dev/fd/$i1" "/dev/fd/$i2"' >&"$i3" {i1}>&"$i1" {i2}>&"$i2" &
eval 'exec cat "/dev/fd/$o1" "/dev/fd/$o2" -' <&"$o3" {o1}<&"$o1" {o2}<&"$o2"
)
printf '%sn' foo bar | f
(Catatan: Itu tidak akan bekerja pada sistem di mana ksh
menggunakan socketpairs
bukannya pipes
, dan di mana /dev/fd/n
bekerja seperti di Linux.)
Dalam ksh
, fds di atas 2
ditandai dengan bendera close-on-exec, kecuali jika diteruskan secara eksplisit pada baris perintah. Itu sebabnya kita tidak perlu menutup deskriptor file yang tidak digunakan seperti dengan zsh
—tetapi itu juga mengapa kita harus melakukan {i1}>&$i1
dan gunakan eval
untuk nilai baru $i1
, untuk diteruskan ke tee
dan cat
…
Di bash
ini tidak dapat dilakukan, karena Anda tidak dapat menghindari tanda close-on-exec.
Di atas, ini relatif sederhana, karena kami hanya menggunakan perintah eksternal sederhana. Ini menjadi lebih rumit ketika Anda ingin menggunakan konstruksi shell di sana, dan Anda mulai mengalami bug shell.
Bandingkan di atas dengan yang sama menggunakan pipa bernama:
f() {
mkfifo p{i,o}{1,2,3}
tr a b < pi1 > po1 &
sed 's/./&&/g' < pi2 > po2 &
cut -c2- < pi3 > po3 &
tee pi{1,2} > pi3 &
cat po{1,2,3}
rm -f p{i,o}{1,2,3}
}
printf '%sn' foo bar | f
Kesimpulan
Jika Anda ingin berinteraksi dengan sebuah perintah, gunakan expect
, atau zsh
zpty
, atau pipa bernama.
Jika Anda ingin membuat pipa ledeng mewah dengan pipa, gunakan pipa bernama.
Co-proses dapat melakukan beberapa hal di atas, tetapi bersiaplah untuk melakukan hal yang serius untuk hal-hal yang tidak sepele.