Bagaimana cara membuat pipa dua arah antara dua program?

63

Semua orang tahu bagaimana membuat pipa searah antara dua program (mengikat stdoutdari yang pertama dan stdinsatu detik): first | second.

Tetapi bagaimana cara membuat pipa dua arah, yaitu cross-bind stdindan stdoutdua program? Apakah ada cara mudah untuk melakukannya di shell?


sumber

Jawaban:

30

Jika pipa pada sistem Anda adalah dua arah (seperti pada Solaris 11 dan setidaknya beberapa BSD, tetapi bukan Linux):

cmd1 <&1 | cmd2 >&0

Waspadalah terhadap deadlock.

Perhatikan juga bahwa beberapa versi ksh93 pada beberapa sistem mengimplementasikan pipa ( |) menggunakan pasangan soket . pasangan soket adalah dua arah, tetapi ksh93 secara eksplisit mematikan arah sebaliknya, sehingga perintah di atas tidak akan bekerja dengan ksh93 itu bahkan pada sistem di mana pipa (seperti yang dibuat oleh pipe(2)panggilan sistem) adalah dua arah.

Stéphane Chazelas
sumber
1
apakah ada yang tahu apakah ini bekerja di linux juga? (menggunakan archlinux di sini)
heinrich5991
41

Yah, itu cukup "mudah" dengan pipa bernama ( mkfifo). Saya memberi tanda kutip dengan mudah karena kecuali program dirancang untuk ini, kebuntuan mungkin terjadi.

mkfifo fifo0 fifo1
( prog1 > fifo0 < fifo1 ) &
( prog2 > fifo1 < fifo0 ) &
( exec 30<fifo0 31<fifo1 )      # write can't open until there is a reader
                                # and vice versa if we did it the other way

Sekarang, biasanya ada buffering yang terlibat dalam penulisan stdout. Jadi, misalnya, jika kedua program tersebut adalah:

#!/usr/bin/perl
use 5.010;
say 1;
print while (<>);

Anda akan mengharapkan loop tak terbatas. Namun sebaliknya, keduanya akan menemui jalan buntu; Anda perlu menambahkan $| = 1(atau setara) untuk mematikan buffering output. Kebuntuan disebabkan karena kedua program sedang menunggu sesuatu di stdin, tetapi mereka tidak melihatnya karena duduk di buffer stdout dari program lain, dan belum ditulis ke pipa.

Pembaruan : memasukkan saran dari Stéphane Charzelas dan Joost:

mkfifo fifo0 fifo1
prog1 > fifo0 < fifo1 &
prog2 < fifo0 > fifo1

melakukan hal yang sama, lebih pendek, dan lebih portabel.

derobert
sumber
23
Satu bernama pipa sudah cukup: prog1 < fifo | prog2 > fifo.
Andrey Vihrov
2
@AndreyVihrov itu benar, Anda dapat mengganti pipa anonim dengan salah satu yang bernama. Tapi saya suka simetri :-P
derobert
3
@ user14284: Di Linux, Anda mungkin dapat melakukannya dengan sesuatu seperti prog1 < fifo | tee /dev/stderr | prog2 | tee /dev/stderr > fifo.
Andrey Vihrov
3
Jika Anda berhasil prog2 < fifo0 > fifo1, Anda dapat menghindari tarian kecil Anda dengan exec 30< ...(yang omong-omong hanya bekerja dengan bashatau yashuntuk fds lebih dari 10 seperti itu).
Stéphane Chazelas
1
@ Joost Hmmm, tampaknya Anda benar bahwa tidak perlu, setidaknya dalam bash. Saya mungkin khawatir bahwa karena shell melakukan pengalihan (termasuk membuka pipa), itu mungkin menghalangi — tetapi setidaknya bash fork sebelum membuka fifos. dashtampaknya baik-baik saja (tetapi berperilaku sedikit berbeda)
derobert
13

Saya tidak yakin apakah ini yang ingin Anda lakukan:

nc -l -p 8096 -c second &
nc -c first 127.0.0.1 8096 &

Ini dimulai dengan membuka soket pendengaran pada port 8096, dan sekali koneksi dibuat, memunculkan program seconddengan itu stdinsebagai output stream dan stdoutsebagai input stream.

Kemudian, yang kedua ncdiluncurkan yang menghubungkan ke port mendengarkan dan memunculkan program firstdengan stdoutsebagai input stream dan stdinsebagai output stream.

Ini tidak sepenuhnya dilakukan menggunakan pipa, tetapi tampaknya melakukan apa yang Anda butuhkan.

Karena ini menggunakan jaringan, ini dapat dilakukan pada 2 komputer jarak jauh. Ini hampir seperti cara server web ( second) dan browser web ( first) bekerja.

jfg956
sumber
1
Dan nc -Uuntuk soket domain UNIX yang hanya mengambil ruang alamat filesystem.
Ciro Santilli 新疆 改造 中心 法轮功 六四 事件
Opsi -c tidak tersedia di Linux. Kebahagiaan saya adalah yang berumur pendek :-(
pablochacin
9

Anda dapat menggunakan pipexec :

$ pipexec -- '[A' cmd1 ] '[B' cmd2 ] '{A:1>B:0}' '{B:1>A:0}'
Andreas Florath
sumber
6

bashversi 4 memiliki coprocperintah yang memungkinkan ini dilakukan dalam murni bashtanpa pipa bernama:

coproc cmd1
eval "exec cmd2 <&${COPROC[0]} >&${COPROC[1]}"

Beberapa cangkang lain juga bisa melakukannya coproc.

Di bawah ini adalah jawaban yang lebih terperinci tetapi mengaitkan tiga perintah, bukan dua, yang membuat hanya sedikit lebih menarik.

Jika Anda senang juga menggunakan catdan stdbufkemudian membangun dapat dibuat lebih mudah dimengerti.

Penggunaan versi bashdengan catdan stdbuf, mudah dimengerti:

# start pipeline
coproc {
    cmd1 | cmd2 | cmd3
}
# create command to reconnect STDOUT `cmd3` to STDIN of `cmd1`
endcmd="exec stdbuf -i0 -o0 /bin/cat <&${COPROC[0]} >&${COPROC[1]}"
# eval the command.
eval "${endcmd}"

Catatan, harus menggunakan eval karena ekspansi variabel dalam <& $ var adalah ilegal di versio of bash 4.2.25 saya.

Versi menggunakan pure bash: Pecah menjadi dua bagian, luncurkan pipa pertama di bawah coproc, lalu makan siang bagian kedua, (baik perintah tunggal atau pipa) menghubungkan kembali ke yang pertama:

coproc {
    cmd 1 | cmd2
}
endcmd="exec cmd3 <&${COPROC[0]} >&${COPROC[1]}"
eval "${endcmd}"

Bukti dari konsep:

file ./prog, hanya prog dummy untuk mengkonsumsi, menandai dan mencetak ulang baris. Menggunakan subkulit untuk menghindari masalah buffering mungkin berlebihan, bukan itu intinya di sini.

#!/bin/bash
let c=0
sleep 2

[ "$1" == "1" ] && ( echo start )

while : ; do
  line=$( head -1 )
  echo "$1:${c} ${line}" 1>&2
  sleep 2
  ( echo "$1:${c} ${line}" )
  let c++
  [ $c -eq 3 ] && exit
done

file ./start_cat Ini adalah versi yang menggunakan bash, catdanstdbuf

#!/bin/bash

echo starting first cmd>&2

coproc {
  stdbuf -i0 -o0 ./prog 1 \
    | stdbuf -i0 -o0 ./prog 2 \
    | stdbuf -i0 -o0 ./prog 3
}

echo "Delaying remainer" 1>&2
sleep 5
cmd="exec stdbuf -i0 -o0 /bin/cat <&${COPROC[0]} >&${COPROC[1]}"

echo "Running: ${cmd}" >&2
eval "${cmd}"

atau file ./start_part. Ini adalah versi yang menggunakan murni bashsaja. Untuk keperluan demo saya masih menggunakan stdbufkarena prog Anda yang sebenarnya harus berurusan dengan buffering secara internal untuk menghindari pemblokiran karena buffering.

#!/bin/bash

echo starting first cmd>&2

coproc {
  stdbuf -i0 -o0 ./prog 1 \
    | stdbuf -i0 -o0 ./prog 2
}

echo "Delaying remainer" 1>&2
sleep 5
cmd="exec stdbuf -i0 -o0 ./prog 3 <&${COPROC[0]} >&${COPROC[1]}"

echo "Running: ${cmd}" >&2
eval "${cmd}"

Keluaran:

> ~/iolooptest$ ./start_part
starting first cmd
Delaying remainer
2:0 start
Running: exec stdbuf -i0 -o0 ./prog 3 <&63 >&60
3:0 2:0 start
1:0 3:0 2:0 start
2:1 1:0 3:0 2:0 start
3:1 2:1 1:0 3:0 2:0 start
1:1 3:1 2:1 1:0 3:0 2:0 start
2:2 1:1 3:1 2:1 1:0 3:0 2:0 start
3:2 2:2 1:1 3:1 2:1 1:0 3:0 2:0 start
1:2 3:2 2:2 1:1 3:1 2:1 1:0 3:0 2:0 start

Itu berhasil.

AnyDev
sumber
5

Sebuah blok bangunan yang nyaman untuk menulis pipa dua arah seperti itu adalah sesuatu yang menghubungkan stdout dan stdin dari proses saat ini bersama-sama. Mari kita menyebutnya ioloop. Setelah memanggil fungsi ini, Anda hanya perlu memulai pipa biasa:

ioloop &&     # stdout -> stdin 
cmd1 | cmd2   # stdin -> cmd1 -> cmd2 -> stdout (-> back to stdin)

Jika Anda tidak ingin memodifikasi deskriptor shell tingkat atas, jalankan ini dalam subkulit:

( ioloop && cmd1 | cmd2 )

Berikut ini adalah implementasi portabel ioloop menggunakan pipa bernama:

ioloop() {
    FIFO=$(mktemp -u /tmp/ioloop_$$_XXXXXX ) &&
    trap "rm -f $FIFO" EXIT &&
    mkfifo $FIFO &&
    ( : <$FIFO & ) &&    # avoid deadlock on opening pipe
    exec >$FIFO <$FIFO
}

Pipa bernama ada di sistem file hanya sebentar selama pengaturan ioloop. Fungsi ini tidak cukup POSIX karena mktemp sudah usang (dan berpotensi rentan terhadap serangan ras).

Implementasi khusus linux menggunakan / proc / mungkin yang tidak memerlukan pipa bernama, tapi saya pikir ini lebih dari cukup baik.

pengguna873942
sumber
Fungsi menarik, +1. Mungkin bisa menggunakan kalimat atau 2 tambahan yang menjelaskan ( : <$FIFO & )lebih detail. Terima kasih telah memposting.
Alex Stragies
Saya melakukan sedikit melihat sekeliling dan muncul kosong; di mana saya dapat menemukan informasi tentang penghentian mktemp? Saya menggunakannya secara luas, dan jika alat yang lebih baru telah mengambil tempatnya, saya ingin mulai menggunakannya.
DopeGhoti
Alex: syscall terbuka (2) pada pipa naned menghalangi. jika Anda mencoba "exec <$ PIPE> $ PIPE" itu akan macet menunggu proses lain untuk membuka sisi lain. Perintah ": <$ FIFO &" dijalankan dalam subkulit di latar belakang dan memungkinkan pengalihan dua arah berhasil diselesaikan.
user873942
DopeGhoti: fungsi pustaka mktemp (3) C sudah tidak digunakan lagi. Utilitas mktemp (1) tidak.
user873942
4

Ada juga

  • dpipe, "pipa dua arah", termasuk dalam paket vde2, dan termasuk dalam sistem manajemen paket distro saat ini .

    dpipe processA = processB

  • socat , alat koneksi segalanya untuk semua.

    socat EXEC:Program1 EXEC:Program2

Ketika @ StéphaneChazelas mencatat dengan benar di komentar, contoh di atas adalah "bentuk dasar", ia memiliki contoh yang bagus dengan pilihan jawaban untuk pertanyaan serupa .

Alex Stragies
sumber
Perhatikan bahwa secara default, socatgunakan soket alih-alih pipa (Anda dapat mengubahnya dengan commtype=pipes). Anda mungkin ingin menambahkan noforkopsi untuk menghindari proses socat ekstra yang mendorong data antar pipa / soket. (terima kasih atas suntingan jawaban saya btw)
Stéphane Chazelas
0

Ada banyak jawaban bagus di sini. Jadi saya hanya ingin menambahkan sesuatu untuk bermain-main dengan mereka. Saya berasumsi stderrtidak diarahkan ke mana pun. Buat dua skrip (misalkan a.sh dan b.sh):

#!/bin/bash
echo "foo" # change to 'bar' in second file

for i in {1..10}; do
  read input
  echo ${input}
  echo ${i} ${0} got: ${input} >&2
done

Kemudian ketika Anda menghubungkannya dengan cara yang baik, Anda akan melihat di konsol:

1 ./a.sh got: bar
1 ./b.sh got: foo
2 ./a.sh got: foo
2 ./b.sh got: bar
3 ./a.sh got: bar
3 ./b.sh got: foo
4 ./a.sh got: foo
4 ./b.sh got: bar
...
pawel7318
sumber