İki program arasında çift yönlü boru nasıl yapılır?


63

Herkes İki program arasındaki (bind tek yönlü boru yapmak bilir stdoutbirincinin ve stdinikinci birinin): first | second.

Ancak çift yönlü boru, yani çapraz bağlama stdinve stdoutiki program nasıl yapılır? Bir kabuğun içinde yapmanın kolay bir yolu var mı?

shell  pipe 

Yanıtlar:


30

Sisteminizdeki borular iki yönlü ise (Solaris 11 ve bazı BSD'lerde olduğu gibi, en azından Linux değil):

cmd1 <&1 | cmd2 >&0

Yine de çıkmaza dikkat edin.

Ayrıca, bazı sistemlerdeki ksh93'ün bazı sürümlerinin |bir soket çifti kullanarak borular ( ) kullandığını unutmayın . soket çiftleri çift yönlüdür, ancak ksh93 açıkça ters yönde kapanıyor, bu nedenle yukarıdaki komut, boruların ( pipe(2)sistem çağrısı tarafından yaratıldığı gibi ) çift yönlü olduğu sistemlerde bile bu ksh93'lerle çalışmadı .


1
Bunun Linux üzerinde de çalışıp çalışmadığını bilen var mı? (Burada archlinux kullanarak)
heinrich5991


41

Eh, adlandırılmış boru ( mkfifo) ile oldukça "kolay" . Kolayca alıntı yapıyorum çünkü programlar bunun için tasarlanmadığı sürece kilitlenme olasıdır.

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

Şimdi normal olarak stdout yazma ile ilgili tamponlama vardır. Yani, örneğin, her iki program da olsaydı:

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

sonsuz bir döngü beklersiniz. Fakat bunun yerine her ikisi de kilitlenecekti; $| = 1çıktı tamponlamayı kapatmak için (veya eşdeğeri) eklemeniz gerekir . Bu kilitlenme, her iki programın da stdin'de bir şey beklediğinden, ancak onu görmüyor çünkü diğer programın stdout tamponunda oturuyor ve henüz boruya yazılmadı.

Güncelleme : Stéphane Charzelas ve Joost'un önerilerini içeren:

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

Aynısını yapar, daha kısa ve daha taşınabilir.


23
Bir adlandırılmış kanal yeterlidir: prog1 < fifo | prog2 > fifo.
Andrey Vihrov

2
@AndreyVihrov bu doğru, adlandırılmış olanlardan biri için isimsiz bir boru kullanabilirsiniz. Ama simetriyi
sevdim

3
@ user14284: Linux'ta muhtemelen böyle bir şeyle yapabilirsiniz prog1 < fifo | tee /dev/stderr | prog2 | tee /dev/stderr > fifo.
Andrey Vihrov

3
Bunu yaparsanız prog2 < fifo0 > fifo1, size ile küçük dansı önleyebilirsiniz exec 30< ...(bu arada sadece çalışır bashveya yashböyle 10 üzerinde fds için).
Stéphane Chazelas

1
@Joost Hmmm, hiç gerek yok, en azından bash olarak, haklı görünüyorsunuz. Büyük olasılıkla kabuk yönlendirme yaptıklarından (boruların açılması dahil) engelleyebileceğinden endişe ediyordum - ama en azından beşleri açmadan önce çatal bıçakları. dashde Tamam görünüyor (ama biraz farklı davranıyor)
derobert 17

13

Yapmaya çalıştığın şeyin bu olup olmadığından emin değilim:

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

Bu port 8096 tarihinde dinleme soketi açarak başlar ve bir bağlantı sağlandıktan sonra, programı olarak çoğaltılır secondonun ile stdinakışı olarak çıktı ve stdoutakışı girişi olarak.

Sonra, ikinci bir ncdinleme bağlantı noktasına bağlanır ve programı olarak çoğaltılır hangi başlatılır firstile onun stdoutakışı girişi ve yanı stdinakışı olarak çıktı.

Bu tam olarak bir boru kullanılarak yapılmadı, ancak ihtiyacınız olanı yapıyor gibi görünüyor.

Bu ağı kullandığından, bu 2 uzak bilgisayarda yapılabilir. Neredeyse bir web sunucusu ( second) ve bir web tarayıcısı ( first) bu şekilde çalışır.


1
Ve nc -Uyalnızca dosya sistemi adres alanını alan UNIX etki alanı soketleri için.
Ciro Santilli 事件 改造 5 法轮功 六四 事件

-c seçeneği Linux'ta mevcut değildir. Mutluluğum kısa ömürlü biriydi :-(
pablochacin


6

bashsürüm 4, coprocbunun bashadlandırılmış yöneltmeler olmadan saf olarak yapılmasına izin veren bir komuta sahiptir :

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

Bazı diğer kabukları da yapabilir coproc.

Aşağıda daha ayrıntılı bir cevap var, ancak sadece biraz daha ilginç kılan iki komut yerine iki komut yerine.

Eğer kullanmaktan da memnunsanız catve stdbufyapımı daha sonra anlamak daha kolay hale getirilebilir.

Sürüm kullanarak bashile catve stdbufkolay anlaşılması:

# 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}"

Unutmayın, eval kullanmalısınız çünkü <& $ var değişkenindeki genişleme bash 4.2.25 versiyonumda geçersizdir.

Pure kullanarak sürüm bash: İki parçaya bölün, ilk boru hattını coproc altında çalıştırın, sonra öğle yemeği ikinci kısmını (ya tek bir komut ya da bir boru hattı) ilkine yeniden bağlayın:

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

Kavramın ispatı:

dosya ./prog, sadece tüketmek, etiketlemek ve satırları yeniden yazdırmak için sahte bir prog. Arabellek sorunlarından kaçınmak için alt kabukları kullanmak, muhtemelen fazla önemsiz, burada mesele değil.

#!/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

dosya ./start_cat Bu kullanarak bir sürümüdür bash, catvestdbuf

#!/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}"

veya dosya ./start_part. Bu bashsadece saf kullanan bir versiyondur . Demo amaçlı olarak, hala kullanıyorum stdbufçünkü gerçek programınız arabelleğe alma nedeniyle engellemeyi önlemek için zaten dahili olarak tamponlama yapmak zorunda kalacak.

#!/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}"

Çıktı:

> ~/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

Bu yapar.


5

Bu iki yönlü boruları yazmak için uygun bir yapı bloğu, mevcut sürecin stdout ve stdin'ini birbirine bağlayan bir şeydir. Buna iyiyop diyelim. Bu işlevi çağırdıktan sonra, yalnızca normal bir boru başlatmanız gerekir:

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

Üst düzey kabuğun tanımlayıcılarını değiştirmek istemiyorsanız, bunu bir alt kabukta çalıştırın:

( ioloop && cmd1 | cmd2 )

İşte adlandırılmış bir boru kullanarak ioloop'un taşınabilir bir uygulaması:

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

Adlandırılmış olan boru dosya sisteminde sadece ioloop kurulumu sırasında kısa bir süredir. Bu işlev oldukça POSIX değildir çünkü mktemp kullanımdan kaldırılmıştır (ve bir yarış saldırısına karşı savunmasızdır).

/ Proc / kullanarak linux'a özgü bir uygulama, adlandırılmış bir boru gerektirmeyen mümkündür, ancak bence bu yeterince iyi.


İlginç fonksiyon, +1. Muhtemelen ( : <$FIFO & )daha ayrıntılı bir şekilde açıklayan bir cümle veya 2 ek kullanabilirsiniz . Gönderdiğiniz için teşekkür ederiz.
Alex Stragies

Biraz etrafa baktım ve boş çıktım; itiraz hakkında bilgi nereden bulabilirim mktemp? Kapsamlı kullanıyorum ve daha yeni bir araç yerini aldıysa, kullanmaya başlamak istiyorum.
DopeGhoti,

Alex: Naneli bir borudaki açık (2) sistem engelleniyor. Eğer "exec <$ PIPE> $ PIPE" yapmayı denerseniz, diğer tarafın açılması için başka bir işlemin yapılmasını beklemek sıkışır. ": <$ FIFO &" komutu arka planda bir alt kabukta yürütülür ve çift yönlü yönlendirmenin başarıyla tamamlanmasını sağlar.
user873942,

DopeGhoti: mktemp (3) C kütüphanesi işlevi kullanımdan kaldırılmıştır. Mktemp (1) yardımcı programı değil.
user873942

4

Ayrıca birde şu var

@ StéphaneChazelas yorumlarda doğru bir şekilde not ettiği gibi, yukarıdaki örnekler “temel biçim” dir, benzer bir soruya cevabında seçenekleriyle güzel örnekleri vardır .


Varsayılan olarak, socatborular yerine soketler kullandığına dikkat edin (bunu ile değiştirebilirsiniz commtype=pipes). noforkBorular / soketler arasında veri toplayan ekstra bir socat işleminden kaçınmak için bir seçenek eklemek isteyebilirsiniz . (üzerinde düzenleme için teşekkürler Cevabıma btw)
Stéphane Chazelas

0

Burada çok güzel cevaplar var. Bu yüzden sadece onlarla oynamak kolay bir şey eklemek istiyorum. Sanırım stderrhiçbir yere yönlendirilmemiş. İki komut dosyası oluşturun (a.sh ve b.sh diyelim):

#!/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

Sonra onları iyi bir şekilde bağladığınızda konsolda görmeniz gerekir:

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
...
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.