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


63

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

しかし、双方向パイプ、つまりクロスバインドstdinstdout2つのプログラムの作成方法は?シェルでそれを行う簡単な方法はありますか?

shell  pipe 

回答:


30

システム上のパイプが双方向である場合(少なくともSolaris 11および一部のBSD上であり、Linux上ではないため):

cmd1 <&1 | cmd2 >&0

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

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


1
誰もこれがLinuxでも動作するかどうか知っていますか?(ここでarchlinuxを使用)
heinrich5991


41

さて、名前付きパイプ(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

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


23
名前付きパイプは1つで十分ですprog1 < fifo | prog2 > fifo
アンドレイVihrov

2
@AndreyVihrovそれは本当です。名前付きパイプの1つを匿名パイプに置き換えることができます。しかし、私は対称性が好きです:-P
derobert

3
@ user14284:Linuxでは、おそらくのようなものでそれを行うことができますprog1 < fifo | tee /dev/stderr | prog2 | tee /dev/stderr > fifo
アンドレイVihrov

3
あなたはそれを作る場合prog2 < fifo0 > fifo1、あなたはとあなたの小さなダンスを避けることができますexec 30< ...(ちなみにのみで動作しますbashyashそのような10以上FDSのために)。
ステファンシャゼル14

1
@Joost Hmmm、少なくともbashでは必要ないというのは正しいようです。シェルがリダイレクトを実行する(パイプを開くことを含む)ので、ブロックするかもしれませんが、少なくともfifoを開く前にbashフォークするのではないかと心配していました。dash大丈夫だと思われる(ただし、動作は少し異なります)
デロバート

13

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

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

これは、ポート8096で待機ソケットを開くことから始まり、接続が確立されると、ストリーム出力およびストリーム入力としてプログラムsecondを生成します。stdinstdout

次に、ncリスニングポートに接続し、ストリーム入力およびストリーム出力としてプログラムfirstを生成する2つ目が起動します。stdoutstdin

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

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


1
またnc -U、ファイルシステムのアドレス空間のみを使用するUNIXドメインソケットの場合。
Ciro Santilli新疆改造中心法轮功六四事件

-cオプションはLinuxでは使用できません。私の幸せは短命でした:-(
パブロカシン


6

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

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

他のいくつかのシェルもcoproc同様に行うことができます。

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

あなたはまた、使用するために満足している場合catstdbuf、その後構築理解することが容易となります。

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

# 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 これはを使用するバージョン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}"

または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

それはそれを行います。


5

このような双方向パイプを記述するための便利なビルディングブロックは、現在のプロセスの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固有の実装も可能ですが、これで十分だと思います。


興味深い機能、+ 1。おそらく、文章を使用するか、2を追加( : <$FIFO & )して詳細を説明することができます。投稿していただきありがとうございます。
アレックスストラギーズ

私は少し周りを見回して、空っぽになりました。の廃止に関する情報はどこで入手できmktempますか?私はそれを広範に使用していますが、新しいツールに取って代わりましたら、使用を開始したいと思います。
DopeGhoti

Alex:nanedパイプのopen(2)システムコールがブロックされています。「<$ PIPE> $ PIPEを実行」しようとすると、別のプロセスが反対側を開くのを待ってスタックします。コマンド「:<$ FIFO&」はバックグラウンドのサブシェルで実行され、双方向リダイレクトが正常に完了するようにします。
user873942 16

DopeGhoti:mktemp(3)Cライブラリ関数は非推奨です。mktemp(1)ユーティリティはそうではありません。
user873942 16

4

もあります

@StéphaneChazelasがコメントで正しく述べているように、上記の例は「基本フォーム」であるため、同様の質問に対する回答にオプションを付けた素晴らしい例があります


デフォルトでsocatは、パイプの代わりにソケットを使用することに注意してください(これはで変更できますcommtype=pipes)。noforkパイプ/ソケット間でデータを提示する余分なsocatプロセスを回避するために、オプションを追加することができます。(私の答え btwの編集に感謝)
ステファンシャゼラス

0

ここには多くの素晴らしい答えがあります。だから、彼らと簡単に遊ぶために何かを追加したいだけです。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
...
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.