GNU/Linux >> Belajar Linux >  >> Linux

Bagaimana Cara Menggunakan Command Coproc Di Berbagai Shell?

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.)

Terkait:Jalankan aplikasi yang tidak tepercaya dengan aman melalui perintah sandbox-exec?

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.


Linux
  1. Cara menggunakan perintah sed Linux

  2. Cara menggunakan perintah grep Linux

  3. Cara menggunakan perintah history di Linux

  1. Bagaimana cara menggunakan perintah nama dasar?

  2. Cara Menggunakan Perintah id di Linux

  3. Cara menggunakan perintah "layar" di Linux

  1. Cara Menggunakan Perintah vmstat

  2. Cara Menggunakan Perintah sejarah Linux

  3. Cara Menggunakan Perintah baca Bash