誰もが2つのプログラム(stdout
最初のプログラムとstdin
2番目のプログラムのバインド)間で単方向パイプを作成する方法を知っていますfirst | second
。
しかし、双方向パイプ、つまりクロスバインドstdin
とstdout
2つのプログラムの作成方法は?シェルでそれを行う簡単な方法はありますか?
誰もが2つのプログラム(stdout
最初のプログラムとstdin
2番目のプログラムのバインド)間で単方向パイプを作成する方法を知っていますfirst | second
。
しかし、双方向パイプ、つまりクロスバインドstdin
とstdout
2つのプログラムの作成方法は?シェルでそれを行う簡単な方法はありますか?
回答:
システム上のパイプが双方向である場合(少なくともSolaris 11および一部のBSD上であり、Linux上ではないため):
cmd1 <&1 | cmd2 >&0
ただし、デッドロックに注意してください。
また、いくつかのシステムのksh93のいくつかのバージョンがソケットペア|
を使用してpipesを実装することに注意してください。ソケットペアは双方向ですが、ksh93は明示的に逆方向をシャットダウンするため、パイプ(システムコールによって作成される)が双方向であるシステムでも、上記のコマンドはそれらのksh93では機能しません。pipe(2)
さて、名前付きパイプ(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
出力バッファリングをオフにするには、追加(または同等)する必要があります。デッドロックは、両方のプログラムがstdinで何かを待っているために発生しますが、他のプログラムのstdoutバッファーに置かれていて、まだパイプに書き込まれていないため、見えません。
更新:StéphaneCharzelasとJoostからの提案を取り入れる:
mkfifo fifo0 fifo1
prog1 > fifo0 < fifo1 &
prog2 < fifo0 > fifo1
同じことをし、より短く、よりポータブルです。
prog1 < fifo | prog2 > fifo
。
prog1 < fifo | tee /dev/stderr | prog2 | tee /dev/stderr > fifo
。
prog2 < fifo0 > fifo1
、あなたはとあなたの小さなダンスを避けることができますexec 30< ...
(ちなみにのみで動作しますbash
かyash
そのような10以上FDSのために)。
dash
大丈夫だと思われる(ただし、動作は少し異なります)
これがあなたがやろうとしていることであるかどうかはわかりません:
nc -l -p 8096 -c second &
nc -c first 127.0.0.1 8096 &
これは、ポート8096で待機ソケットを開くことから始まり、接続が確立されると、ストリーム出力およびストリーム入力としてプログラムsecond
を生成します。stdin
stdout
次に、nc
リスニングポートに接続し、ストリーム入力およびストリーム出力としてプログラムfirst
を生成する2つ目が起動します。stdout
stdin
これはパイプを使用して正確には行われませんが、必要なことを行うようです。
これはネットワークを使用するため、2台のリモートコンピューターで実行できます。これは、ほぼWebサーバー(second
)とWebブラウザー(first
)の動作方法です。
nc -U
、ファイルシステムのアドレス空間のみを使用するUNIXドメインソケットの場合。
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}"
bash 4.2.25の私のバージョンでは、<&$ varの変数展開は違法であるため、evalを使用する必要があります。
pureを使用したバージョンbash
:2つの部分に分割し、coprocの下で最初のパイプラインを起動し、次に2番目の部分(単一のコマンドまたはパイプライン)を最初のパイプラインに再接続します。
coproc {
cmd 1 | cmd2
}
endcmd="exec cmd3 <&${COPROC[0]} >&${COPROC[1]}"
eval "${endcmd}"
コンセプトの証明:
fileは./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}"
またはfile ./start_part
。これは、純粋bash
のみを使用するバージョンです。デモの目的でstdbuf
、実際のプログラムはバッファリングによるブロッキングを避けるためにとにかく内部でバッファリングを処理する必要があるため、私はまだ使用しています。
#!/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固有の実装も可能ですが、これで十分だと思います。
( : <$FIFO & )
して詳細を説明することができます。投稿していただきありがとうございます。
mktemp
ますか?私はそれを広範に使用していますが、新しいツールに取って代わりましたら、使用を開始したいと思います。
もあります
dpipe
、「双方向パイプ」。vde2パッケージに含まれ、現在のディストリビューションパッケージ管理システムに含まれています。
dpipe processA = processB
socat、すべてを接続するツール。
socat EXEC:Program1 EXEC:Program2
@StéphaneChazelasがコメントで正しく述べているように、上記の例は「基本フォーム」であるため、同様の質問に対する回答にオプションを付けた素晴らしい例があります。
socat
は、パイプの代わりにソケットを使用することに注意してください(これはで変更できますcommtype=pipes
)。nofork
パイプ/ソケット間でデータを提示する余分なsocatプロセスを回避するために、オプションを追加することができます。(私の答え btwの編集に感謝)
ここには多くの素晴らしい答えがあります。だから、彼らと簡単に遊ぶために何かを追加したいだけです。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
...