Jawaban:
co-proses adalah kshfitur (sudah dalam ksh88). zshtelah memiliki fitur dari awal (awal 90-an), sementara itu baru saja ditambahkan bashpada 4.0(2009).
Namun, perilaku dan antarmuka berbeda secara signifikan antara 3 shell.
Idenya adalah sama, meskipun: memungkinkan untuk memulai pekerjaan di latar belakang dan dapat mengirim input dan membaca outputnya tanpa harus menggunakan pipa bernama.
Itu dilakukan dengan pipa tanpa nama dengan sebagian besar cangkang dan soket dengan versi terbaru dari ksh93 pada beberapa sistem.
Dalam a | cmd | b, amengumpankan data ke cmddan bmembaca hasilnya. Berjalan cmdsebagai proses bersama memungkinkan shell menjadi keduanya adan b.
Di ksh, Anda memulai coprocess sebagai:
cmd |&
Anda memberi makan data cmddengan melakukan hal-hal seperti:
echo test >&p
atau
print -p test
Dan baca cmdkeluaran dengan hal-hal seperti:
read var <&p
atau
read -p var
cmddimulai sebagai setiap pekerjaan latar belakang, Anda dapat menggunakan fg, bg, killdi atasnya dan merujuk dengan %job-numberatau melalui $!.
Untuk menutup ujung tulisan dari pipa cmdyang dibaca, Anda dapat melakukan:
exec 3>&p 3>&-
Dan untuk menutup ujung pembacaan dari pipa lain (yang cmdsedang menulis):
exec 3<&p 3<&-
Anda tidak dapat memulai proses bersama kedua kecuali Anda terlebih dahulu menyimpan deskriptor file pipa ke beberapa fds lainnya. Misalnya:
tr a b |&
exec 3>&p 4<&p
tr b c |&
echo aaa >&3
echo bbb >&p
Dalam zsh, co-proses hampir identik dengan yang ada di ksh. Satu-satunya perbedaan nyata adalah bahwa zshproses bersama dimulai dengan coprockata kunci.
coproc cmd
echo test >&p
read var <&p
print -p test
read -p var
Perbuatan:
exec 3>&p
Catatan: Ini tidak memindahkan coprocdeskriptor file ke fd 3(seperti di ksh), tetapi menduplikatnya. Jadi, tidak ada cara eksplisit untuk menutup pipa makan atau membaca, selain memulai yang lain coproc .
Sebagai contoh, untuk menutup makan akhir:
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 co-proses 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, co-proses gaya ksh tidak akan berfungsi, karena program mulai melakukan buffering ketika hasilnya berupa pipa.
Berikut ini beberapa contohnya.
Mulai proses bersama x:
zmodload zsh/zpty
zpty x cmd
(Di sini, cmdadalah perintah sederhana. Tetapi Anda dapat melakukan hal-hal yang lebih menarik evalatau fungsi.)
Umpan data proses bersama:
zpty -w x some data
Baca data proses bersama (dalam kasus paling sederhana):
zpty -r x var
Seperti expect, itu bisa menunggu beberapa output dari co-proses yang cocok dengan pola yang diberikan.
Sintaks bash jauh lebih baru, dan dibangun di atas fitur baru yang baru ditambahkan ke ksh93, bash, dan zsh. Ini memberikan sintaks untuk memungkinkan penanganan deskriptor file yang dialokasikan secara dinamis di atas 10.
bashmenawarkan sintaks dasar coproc , dan yang diperluas .
Sintaks dasar untuk memulai proses-co terlihat seperti zsh:
coproc cmd
Di kshatau zsh, pipa ke dan dari co-proses diakses dengan >&pdan <&p.
Tetapi dalam bash, file deskriptor dari pipa dari co-proses dan pipa lainnya ke co-proses dikembalikan dalam $COPROCarray (masing ${COPROC[0]}- masing dan ${COPROC[1]}. Jadi ...
Umpan 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 saat itu.
Dalam sintaks yang diperluas, Anda dapat memberi nama proses-bersama Anda (seperti dalam zshproses-proses bersama zpty):
coproc mycoproc { cmd; }
Perintah itu harus berupa perintah majemuk. (Perhatikan bagaimana contoh di atas mengingatkan function f { ...; }.)
Kali ini, deskriptor file dalam ${mycoproc[0]}dan ${mycoproc[1]}.
Anda dapat mulai lebih dari satu co-proses pada waktu-tetapi Anda lakukan mendapatkan peringatan ketika Anda mulai co-proses sementara satu 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 penutupan dengan cara itu tidak berfungsi dalam versi bash sebelum 4.3 di mana Anda harus menulisnya:
fd=${tr[1]}
exec {fd}>&-
Seperti pada kshdan zsh, deskriptor file pipa tersebut ditandai sebagai close-on-exec.
Namun dalam bash, satu-satunya cara untuk lulus mereka perintah dieksekusi adalah untuk menduplikasi mereka untuk fds 0, 1atau 2. Itu membatasi jumlah co-proses yang dapat Anda berinteraksi dengan untuk satu perintah. (Lihat contoh di bawah ini.)
yashtidak memiliki fitur co-proses per se, tetapi konsep yang sama dapat diimplementasikan dengan pipeline dan fitur pengalihan proses . yashmemiliki antarmuka untuk pipe()panggilan sistem, sehingga 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 menciptakan a pipe(4,5)(5 ujung penulisan, 4 ujung pembacaan), kemudian mengarahkan fd 3 ke pipa ke proses yang berjalan dengan stdin di ujung yang lain, dan stdout pergi ke 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 flag close-on-exec tidak diatur 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<&-
Proses bersama dapat dengan mudah diimplementasikan dengan pipa bernama standar. Saya tidak tahu kapan tepatnya pipa bernama diperkenalkan tetapi mungkin itu setelah kshmuncul dengan proses bersama (mungkin pada pertengahan 80-an, ksh88 "dirilis" pada tahun 88, tapi saya percaya kshdigunakan secara internal di AT&T beberapa tahun sebelumnya itu) yang akan menjelaskan mengapa.
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 mereka lebih mudah — terutama jika Anda perlu menjalankan lebih dari satu proses bersama. (Lihat contoh di bawah.)
Satu-satunya manfaat menggunakan coprocadalah Anda tidak perlu membersihkan pipa-pipa tersebut setelah digunakan.
Kerang menggunakan pipa dalam beberapa konstruksi:
cmd1 | cmd2 ,$(cmd) ,<(cmd) , >(cmd).Dalam hal itu, data mengalir hanya dalam satu arah antara berbagai proses.
Namun, dengan proses bersama dan pipa bernama, mudah mengalami kebuntuan. Anda harus melacak perintah yang memiliki deskriptor file mana yang terbuka, untuk mencegahnya tetap terbuka dan menahan proses tetap hidup. Kebuntuan dapat menjadi sulit untuk diselidiki, karena mereka mungkin terjadi secara non-deterministik; misalnya, hanya ketika data sebanyak satu pipa diisi dikirim.
expectuntuk apa itu dirancang untukTujuan utama dari co-proses adalah untuk memberikan shell dengan cara untuk berinteraksi dengan perintah. Namun, itu tidak berfungsi dengan baik.
Bentuk kebuntuan paling sederhana yang disebutkan di atas adalah:
tr a b |&
echo a >&p
read var<&p
Karena outputnya tidak masuk ke terminal, trbuffer outputnya. Jadi ia tidak akan mengeluarkan apa-apa sampai ia melihat end-of-file-nya stdin, atau ia telah mengumpulkan buffer-penuh data ke output. Jadi di atas, setelah shell memiliki output a\n(hanya 2 byte), readakan memblokir tanpa batas karena trmenunggu 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 buffer output mereka, atau perintah yang bisa dikatakan tidak buffer output mereka; misalnya, dengan menggunakan stdbufbeberapa perintah pada sistem GNU atau FreeBSD terbaru.
Itu sebabnya expectatau zptygunakan pseudo-terminal saja. expectadalah alat yang dirancang untuk berinteraksi dengan perintah, dan itu melakukannya dengan baik.
Co-proses dapat digunakan untuk melakukan beberapa pipa 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 ingin fungsi yang memberi makan salinan output perintah ke 3 perintah lain, dan kemudian output dari 3 perintah tersebut digabungkan.
Semua menggunakan pipa.
Misalnya: memberi makan output dari printf '%s\n' foo barke tr a b, sed 's/./&&/g'dan cut -b2-untuk mendapatkan sesuatu seperti:
foo
bbr
ffoooo
bbaarr
oo
ar
Pertama, itu belum tentu jelas, tetapi ada kemungkinan kebuntuan di sana, dan itu akan mulai terjadi setelah hanya beberapa kilobyte data.
Kemudian, tergantung pada shell Anda, Anda akan menjalankan sejumlah masalah berbeda yang harus ditangani secara berbeda.
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 '%s\n' foo bar | f
Di atas, co-proses fds memiliki set flag close-on-exec, tetapi bukan flag yang diduplikasi dari mereka (seperti pada {o1}<&p). Jadi, untuk menghindari kebuntuan, Anda harus memastikan mereka tutup dalam proses apa pun yang tidak membutuhkannya.
Demikian pula, kita harus menggunakan subkulit dan menggunakan exec catpada akhirnya, untuk memastikan tidak ada proses shell berbohong tentang memegang pipa 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 '%s\n' foo bar | f
( Catatan: Itu tidak akan berfungsi pada sistem yang kshmenggunakan socketpairsalih-alih pipes, dan di mana /dev/fd/nberfungsi seperti di Linux.)
Dalam ksh, fds di atas 2ditandai dengan flag close-on-exec, kecuali jika dilewatkan secara eksplisit pada baris perintah. Itu sebabnya kita tidak harus menutup deskriptor file yang tidak digunakan seperti dengan zsh—tapi itu juga mengapa kita harus melakukan {i1}>&$i1dan menggunakan evaluntuk nilai baru $i1, untuk diteruskan ke teedan cat...
Dalam bashhal ini tidak dapat dilakukan, karena Anda tidak dapat menghindari flag close-on-exec.
Di atas, ini relatif sederhana, karena kami hanya menggunakan perintah eksternal sederhana. Semakin rumit ketika Anda ingin menggunakan konstruksi shell di sana, dan Anda mulai mengalami bug shell.
Bandingkan yang 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 '%s\n' foo bar | f
Jika Anda ingin berinteraksi dengan perintah, penggunaan expect, atau zsh's zpty, atau bernama pipa.
Jika Anda ingin melakukan pemipaan mewah dengan pipa, gunakan pipa bernama.
Co-proses dapat melakukan beberapa hal di atas, tetapi bersiaplah untuk melakukan beberapa goresan kepala serius untuk apa pun yang tidak sepele.
exec {tr[1]}>&-memang tampaknya bekerja dengan versi yang lebih baru dan direferensikan dalam entri CWRU / changelog ( izinkan kata-kata seperti {array [ind]} menjadi pengalihan yang valid ... 2012-09-01). exec {tr[1]}<&-(atau lebih tepatnya >&-setara meskipun itu tidak ada bedanya karena hanya membutuhkan close()keduanya) tidak menutup stdin coproc, tetapi akhir penulisan pipa ke coproc itu.
yash.
mkfifoadalah Anda tidak perlu khawatir tentang kondisi balapan dan keamanan untuk akses pipa. Anda masih harus khawatir tentang kebuntuan dengan fifo.
stdbufperintah dapat membantu mencegah setidaknya beberapa dari mereka. Saya menggunakannya di Linux dan bash. Pokoknya saya percaya @ StéphaneChazelas benar dalam Kesimpulan: fase "menggaruk kepala" berakhir untuk saya hanya ketika saya kembali ke pipa bernama.
Proses bersama pertama kali diperkenalkan dalam bahasa scripting shell dengan ksh88shell (1988), dan kemudian di zshbeberapa titik sebelum 1993.
Sintaks untuk meluncurkan co-proses di bawah ksh adalah command |&. Mulai dari sana, Anda dapat menulis ke commandinput standar dengan print -pdan membaca output standarnya read -p.
Lebih dari beberapa dekade kemudian, bash yang kurang memiliki fitur ini akhirnya memperkenalkannya pada rilis 4.0-nya. Sayangnya, sintaks yang tidak kompatibel dan lebih kompleks dipilih.
Di bawah bash 4.0 dan yang lebih baru, Anda dapat meluncurkan proses bersama dengan coprocperintah, misalnya:
$ coproc awk '{print $2;fflush();}'
Anda kemudian dapat mengirimkan sesuatu ke perintah stdin seperti itu:
$ echo one two three >&${COPROC[1]}
dan baca output awk dengan:
$ read -ru ${COPROC[0]} foo
$ echo $foo
two
Di bawah ksh, itu akan menjadi:
$ awk '{print $2;fflush();}' |&
$ print -p "one two three"
$ read -p foo
$ echo $foo
two
Apa itu "coproc"?
Ini adalah kependekan dari "co-proses" yang berarti proses kedua bekerja sama dengan shell. Ini sangat mirip dengan pekerjaan latar belakang yang dimulai dengan "&" di akhir perintah, kecuali bahwa alih-alih berbagi input dan output standar yang sama dengan shell induknya, I / O standarnya terhubung ke shell induk dengan spesial jenis pipa yang disebut FIFO. Untuk referensi klik di sini
Seseorang memulai coproc di zsh dengan
coproc command
Perintah harus disiapkan untuk membaca dari stdin dan / atau menulis ke stdout, atau tidak banyak digunakan sebagai coproc.
Baca artikel ini di sini menyediakan studi kasus antara exec dan coproc
|. (yaitu menggunakan pipa di sebagian besar kulit, dan soket di ksh93). pipa dan soket pasang adalah yang pertama masuk, pertama keluar, mereka semua FIFO. mkfifomembuat pipa bernama, proses-proses tidak menggunakan pipa bernama.
Berikut ini contoh lain yang bagus (dan berfungsi) - server sederhana yang ditulis dalam BASH. Harap dicatat bahwa Anda akan membutuhkan OpenBSD netcat, yang klasik tidak akan berfungsi. Tentu saja Anda dapat menggunakan soket inet alih-alih unix satu.
server.sh:
#!/usr/bin/env bash
SOCKET=server.sock
PIDFILE=server.pid
(
exec </dev/null
exec >/dev/null
exec 2>/dev/null
coproc SERVER {
exec nc -l -k -U $SOCKET
}
echo $SERVER_PID > $PIDFILE
{
while read ; do
echo "pong $REPLY"
done
} <&${SERVER[0]} >&${SERVER[1]}
rm -f $PIDFILE
rm -f $SOCKET
) &
disown $!
client.sh:
#!/usr/bin/env bash
SOCKET=server.sock
coproc CLIENT {
exec nc -U $SOCKET
}
{
echo "$@"
read
} <&${CLIENT[0]} >&${CLIENT[1]}
echo $REPLY
Pemakaian:
$ ./server.sh
$ ./client.sh ping
pong ping
$ ./client.sh 12345
pong 12345
$ kill $(cat server.pid)
$
bash 4.3.11, Anda sekarang dapat menutup deskriptor file coproc secara langsung, tanpa perlu sebuah aux. variabel; dalam hal contoh dalam jawaban Andaexec {tr[1]}<&-sekarang akan berfungsi (untuk menutup stdin coproc; perhatikan bahwa kode Anda (secara tidak langsung) mencoba untuk menutup{tr[1]}menggunakan>&-, tetapi stdin{tr[1]}coproc , dan harus ditutup dengan ). Perbaikan pasti datang di suatu tempat antara , yang masih menunjukkan masalah, dan , yang tidak.<&-4.2.254.3.11