Jawaban:
co-proses adalah ksh
fitur (sudah dalam ksh88
). zsh
telah memiliki fitur dari awal (awal 90-an), sementara itu baru saja ditambahkan bash
pada 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
, a
mengumpankan data ke cmd
dan b
membaca hasilnya. Berjalan cmd
sebagai proses bersama memungkinkan shell menjadi keduanya a
dan b
.
Di ksh
, Anda memulai coprocess sebagai:
cmd |&
Anda memberi makan data 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 setiap pekerjaan latar belakang, Anda dapat menggunakan fg
, bg
, kill
di atasnya dan merujuk dengan %job-number
atau melalui $!
.
Untuk menutup ujung tulisan dari pipa cmd
yang dibaca, Anda dapat melakukan:
exec 3>&p 3>&-
Dan untuk menutup ujung pembacaan dari pipa lain (yang cmd
sedang 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 zsh
proses bersama dimulai dengan coproc
kata kunci.
coproc cmd
echo test >&p
read var <&p
print -p test
read -p var
Perbuatan:
exec 3>&p
Catatan: Ini tidak memindahkan coproc
deskriptor 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, cmd
adalah perintah sederhana. Tetapi Anda dapat melakukan hal-hal yang lebih menarik eval
atau 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.
bash
menawarkan sintaks dasar coproc
, dan yang diperluas .
Sintaks dasar untuk memulai proses-co terlihat seperti zsh
:
coproc cmd
Di ksh
atau zsh
, pipa ke dan dari co-proses diakses dengan >&p
dan <&p
.
Tetapi dalam bash
, file deskriptor dari pipa dari co-proses dan pipa lainnya ke co-proses dikembalikan dalam $COPROC
array (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 zsh
proses-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 ksh
dan 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
, 1
atau 2
. Itu membatasi jumlah co-proses yang dapat Anda berinteraksi dengan untuk satu perintah. (Lihat contoh di bawah ini.)
yash
tidak memiliki fitur co-proses per se, tetapi konsep yang sama dapat diimplementasikan dengan pipeline dan fitur pengalihan proses . yash
memiliki 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 ksh
muncul dengan proses bersama (mungkin pada pertengahan 80-an, ksh88 "dirilis" pada tahun 88, tapi saya percaya ksh
digunakan 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 coproc
adalah 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.
expect
untuk 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, tr
buffer 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), read
akan memblokir tanpa batas karena tr
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 buffer output mereka, atau perintah yang bisa dikatakan tidak buffer output mereka; misalnya, dengan menggunakan stdbuf
beberapa perintah pada sistem GNU atau FreeBSD terbaru.
Itu sebabnya expect
atau zpty
gunakan pseudo-terminal saja. expect
adalah 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 bar
ke 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 cat
pada 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 ksh
menggunakan socketpairs
alih-alih pipes
, dan di mana /dev/fd/n
berfungsi seperti di Linux.)
Dalam ksh
, fds di atas 2
ditandai 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}>&$i1
dan menggunakan eval
untuk nilai baru $i1
, untuk diteruskan ke tee
dan cat
...
Dalam bash
hal 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
.
mkfifo
adalah Anda tidak perlu khawatir tentang kondisi balapan dan keamanan untuk akses pipa. Anda masih harus khawatir tentang kebuntuan dengan fifo.
stdbuf
perintah 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 ksh88
shell (1988), dan kemudian di zsh
beberapa titik sebelum 1993.
Sintaks untuk meluncurkan co-proses di bawah ksh adalah command |&
. Mulai dari sana, Anda dapat menulis ke command
input standar dengan print -p
dan 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 coproc
perintah, 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. mkfifo
membuat 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.25
4.3.11