web-dev-qa-db-ja.com

2つのプログラム間で双方向パイプを作成する方法は?

誰もが2つのプログラム間で一方向パイプを作成する方法を知っています(最初のプログラムのstdoutと2番目のプログラムのstdinをバインドします):first | second

しかし、双方向パイプ、つまり2つのプログラムのstdinstdoutを相互バインドする方法を教えてください。シェルでそれを行う簡単な方法はありますか?

66
user14284

システムのパイプが双方向である場合(Solaris 11と一部のBSDではLinuxはそうですが):

_cmd1 <&1 | cmd2 >&0
_

ただし、デッドロックに注意してください。

また、一部のシステムでは、一部のバージョンのksh93がソケットペアを使用してパイプ(_|_)を実装していることに注意してください。ソケットペアは双方向ですが、ksh93は明示的に逆方向をシャットダウンするため、上記のコマンドは、パイプ(pipe(2)システムコールによって作成される)が双方向であるシステム上でも、これらのksh93で機能しません。

32

まあ、名前付きパイプ(mkfifo)を使用すると、かなり簡単です。プログラムがこのように設計されていない限り、デッドロックが発生する可能性があるため、簡単に引用符で囲みます。

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

現在、stdoutの書き込みには通常、バッファリングが関係しています。したがって、たとえば、両方のプログラムが次の場合:

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

あなたは無限ループを期待するでしょう。しかし、代わりに両方がデッドロックします。追加する必要があります$| = 1(または同等のもの)は、出力バッファリングをオフにします。 bothプログラムがstdinで何かを待っているためにデッドロックが発生しますが、他のプログラムのstdoutバッファーにあるため、デッドロックは表示されません、まだパイプに書き込まれていません。

pdate:StéphaneCharzelasおよびJoostからの提案を組み込む:

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

同じことをし、より短く、よりポータブルです。

43
derobert

これがあなたがやろうとしていることかどうかはわかりません:

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

これは、ポート8096でリスニングソケットを開くことから始まります。接続が確立されると、プログラムsecondを、そのstdinをストリーム出力として、stdoutをストリーム入力として生成します。

次に、2番目のncが起動され、リスニングポートに接続して、プログラムfirstを、そのstdoutをストリーム入力として、そのstdinをストリームとして生成します。出力。

これはパイプを使用して正確に行われるわけではありませんが、必要なことを行うようです。

これはネットワークを使用するため、2台のリモートコンピューターで実行できます。これは、ほとんどWebサーバー(second)とWebブラウザー(first)が動作する方法です。

13
jfg956

pipexec を使用できます:

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

bashバージョン4には、名前付きパイプなしで純粋なcoprocでこれを実行できるbashコマンドがあります。

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

他のいくつかのシェルもcoprocを実行できます。

以下はより詳細な回答ですが、2つではなく3つのコマンドをチェーンしているため、少しだけ興味深いです。

catstdbufも使用できる場合は、構成を理解しやすくすることができます。

bashcatおよびstdbufを使用したバージョン、わかりやすい:

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

<&$ varでの変数の展開は、私のbash 4.2.25のバージョンでは違法なので、evalを使用する必要があることに注意してください。

純粋なbashを使用するバージョン:2つの部分に分割し、最初のパイプラインをcoprocで起動し、次に2番目の部分(単一のコマンドまたはパイプラインのいずれか)をランチして、最初の部分に再接続します。

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

コンセプトの証明:

ファイル./prog、行を消費、タグ付け、再印刷するためのダミーのprogです。バッファリングの問題を回避するためにサブシェルを使用するのはやり過ぎかもしれませんが、ここでは重要ではありません。

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

ファイル./start_catこれはbashcatおよびstdbufを使用したバージョンです

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

またはファイル./start_part。これは純粋なbashのみを使用したバージョンです。デモのために私はまだstdbufを使用しています。これは、バッファリングによるブロックを回避するために、実際のprogが内部でバッファリングを処理する必要があるためです。

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

出力:

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

それはそれをします。

7
AnyDev

このような双方向パイプを作成するための便利なビルディングブロックは、現在のプロセスのstdoutとstdinを接続するものです。それをioloopと呼びましょう。この関数を呼び出した後は、通常のパイプを開始するだけで済みます。

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

トップレベルのシェルの記述子を変更したくない場合は、サブシェルでこれを実行します。

( ioloop && cmd1 | cmd2 )

以下は、名前付きパイプを使用したioloopの移植可能な実装です。

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

名前付きパイプは、ioloopのセットアップ中に一時的にファイルシステムに存在します。 mktempは非推奨であるため(そして競合攻撃に対して脆弱である可能性があるため)、この関数はPOSIXとはまったく異なります。

名前付きパイプを必要としない、/ proc /を使用したLinux固有の実装が可能ですが、これは十分に優れていると思います。

5
user873942

もあります

@StéphaneChazelasがコメントで正しく注記しているように、上記の例は「ベースフォーム」です。彼は 同様の質問 の回答に オプションのある良い例 を持っています。

5
Alex Stragies

ここには多くの素晴らしい答えがあります。だから私は彼らと一緒に簡単に遊ぶために何かを追加したいだけです。 stderrはどこにもリダイレクトされないと思います。 2つのスクリプトを作成します(a.shと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

次に、それらをコンソールに表示する適切な方法で接続すると、次のようになります。

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
...
0
pawel7318