誰もが2つのプログラム間で一方向パイプを作成する方法を知っています(最初のプログラムのstdout
と2番目のプログラムのstdin
をバインドします):first | second
。
しかし、双方向パイプ、つまり2つのプログラムのstdin
とstdout
を相互バインドする方法を教えてください。シェルでそれを行う簡単な方法はありますか?
システムのパイプが双方向である場合(Solaris 11と一部のBSDではLinuxはそうですが):
_cmd1 <&1 | cmd2 >&0
_
ただし、デッドロックに注意してください。
また、一部のシステムでは、一部のバージョンのksh93がソケットペアを使用してパイプ(_|
_)を実装していることに注意してください。ソケットペアは双方向ですが、ksh93は明示的に逆方向をシャットダウンするため、上記のコマンドは、パイプ(pipe(2)
システムコールによって作成される)が双方向であるシステム上でも、これらのksh93で機能しません。
まあ、名前付きパイプ(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
同じことをし、より短く、よりポータブルです。
これがあなたがやろうとしていることかどうかはわかりません:
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
)が動作する方法です。
pipexec を使用できます:
$ pipexec -- '[A' cmd1 ] '[B' cmd2 ] '{A:1>B:0}' '{B:1>A:0}'
bash
バージョン4には、名前付きパイプなしで純粋なcoproc
でこれを実行できるbash
コマンドがあります。
coproc cmd1
eval "exec cmd2 <&${COPROC[0]} >&${COPROC[1]}"
他のいくつかのシェルもcoproc
を実行できます。
以下はより詳細な回答ですが、2つではなく3つのコマンドをチェーンしているため、少しだけ興味深いです。
cat
とstdbuf
も使用できる場合は、構成を理解しやすくすることができます。
bash
とcat
および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
これはbash
、cat
および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
それはそれをします。
このような双方向パイプを作成するための便利なビルディングブロックは、現在のプロセスの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固有の実装が可能ですが、これは十分に優れていると思います。
もあります
dpipe
、「双方向パイプ」、vde2パッケージに含まれ、 現在のディストリビューションパッケージ管理システム に含まれています。
dpipe processA = processB
socat 、すべてをすべてに接続するツール。
socat EXEC:Program1 EXEC:Program2
@StéphaneChazelasがコメントで正しく注記しているように、上記の例は「ベースフォーム」です。彼は 同様の質問 の回答に オプションのある良い例 を持っています。
ここには多くの素晴らしい答えがあります。だから私は彼らと一緒に簡単に遊ぶために何かを追加したいだけです。 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
...