名前付きパイプを使用してプロセス間で渡す方法は?

名前付きパイプを使用してプロセス間で渡す方法は?

/tmp/in、プロセスによって生成され開かれた/tmp/out名前付きパイプです(それぞれ読み取り、書き込み、書き込み用)。/tmp/err

私は新しいプロセスを作成し、そのstdinをそれにパイプし/tmp/in、その内容をstdoutに書き、可能であれば/tmp/outその内容をstderrに書きたいと思います。/tmp/errすべてが一つでなければなりません。ラインバッファファッション。このプロセスは、作成された他のプロセスが/tmp/in読み取りを停止して閉じたときに終了する必要があります/tmp/in。このソリューションは、追加のパッケージをインストールせずにUbuntuで実行する必要があります。 bashスクリプトで解決したいです。


マックサイフそうでなければ指摘SSCCE、私が欲しいものを理解するのは難しいです。以下はSSCCEです。しかし、これは最小限の例なので、非常に愚かなことに注意してください。

元の設定

親プロセスは子プロセスを開始し、子プロセスのstdinとstdoutを介して1行ずつ通信します。これを実行すると、次のような結果が得られます。

$ python parent.py 
Parent writes to child:  a
Response from the child: A

Parent writes to child:  b
Response from the child: B

Parent writes to child:  c
Response from the child: C

Parent writes to child:  d
Response from the child: D

Parent writes to child:  e
Response from the child: E

Waiting for the child to terminate...
Done!
$ 

parent.py

from __future__ import print_function
from subprocess import Popen, PIPE
import os

child = Popen('./child.py', stdin=PIPE, stdout=PIPE)
child_stdin  = os.fdopen(os.dup(child.stdin.fileno()), 'w')
child_stdout = os.fdopen(os.dup(child.stdout.fileno()))

for letter in 'abcde':
    print('Parent writes to child: ', letter)
    child_stdin.write(letter+'\n')
    child_stdin.flush()
    response = child_stdout.readline()
    print('Response from the child:', response)
    assert response.rstrip() == letter.upper(), 'Wrong response'

child_stdin.write('quit\n')
child_stdin.flush()
print('Waiting for the child to terminate...')
child.wait()
print('Done!')

子供.py,実行可能でなければなりません!

#!/usr/bin/env python
from __future__ import print_function
from sys import stdin, stdout

while True:
    line = stdin.readline()
    if line == 'quit\n':
        quit()
    stdout.write(line.upper())
    stdout.flush()

必須設定とハッキングソリューション

親ソースファイルまたは子ソースファイルはすべて編集できません。

child.pyの名前をchild_original.pyに変更し、実行可能にしました。その後、実行する前にchild_original.py自体を起動するchild.py(プロキシまたはメディエータ)と呼ばれるbashスクリプトを配置し、python parent.pyParent.pyに偽のchild.pyを呼び出すようにしました。今私のbashスクリプトは親間で渡されます。 .pyとchild_original.py。

fakechild.py

#!/bin/bash
parent=$$
cat std_out &
(head -n 1 shutdown; kill -9 $parent) &
cat >>std_in

start_child.sh親プロセスを実行する前にchild_original.pyを起動してください。

#!/bin/bash
rm -f  std_in std_out shutdown
mkfifo std_in std_out shutdown
./child_original.py <std_in >std_out
echo >shutdown
sleep 1s
rm -f  std_in std_out shutdown

実行方法:

$ ./start_child.sh & 
[1] 7503
$ python parent.py 
Parent writes to child:  a
Response from the child: A

Parent writes to child:  b
Response from the child: B

Parent writes to child:  c
Response from the child: C

Parent writes to child:  d
Response from the child: D

Parent writes to child:  e
Response from the child: E

Waiting for the child to terminate...
Done!
$ echo 

[1]+  Done                    ./start_child.sh
$ 

このハッキングソリューションは動作します。私が知っている限り、ラインバッファリングの要件を満たしておらず、child_original.pyがパイプを閉じて、start_child.shが安全にシャットダウンされる可能性があることをstart_child.shに知らせる追加の閉じるfifoがあります。


質問では、要件(ラインバッファリング、child_original.pyがパイプを閉じるときにシャットダウン、追加のパイプを閉じる必要はありません)を満たす偽のchild.py bashスクリプトの改善を求めます。



私が知りたいこと:

  • 高度なAPIを使用してfifoをファイルとして開く場合は、そのファイルを開く必要があります。読み書き、そうでない場合、呼び出しはブロックopenされました。これは非常に直感的です。また、見ることができます名前付きパイプブロックを読み取り専用で開くのはなぜですか?
  • 実際、私の親プロセスはJavaアプリケーションです。 Javaの外部プロセスを使用している場合は、次の場所から外部プロセスのstdoutとstderrを読みます。悪魔スレッド(setDamon(true)このスレッドを呼び出します。今後始めてください)。それ以外の場合、すべての操作が完了してもJVMは永久に停止します。質問には関係ありませんが、他のトラップには以下が含まれます。Runtime.exec() メソッドに関連するトラップバイパス
  • 明らかにバッファリングされていないことはバッファリングされることを意味しますが、バッファがいっぱいになるのを待つのではなく、できるだけ早くフラッシュします。

答え1

シャットダウンとシャットダウン機能を削除すると(安全ではありませんが極端ですが理解できない場合、サブシェルは一部の無実のプロセスを終了する前に死亡する可能性がありますchild.py)、終了しません。素晴らしいUNIX市民のように振る舞います。(head -n 1 shutdown; kill -9 $parent) &kill -9child.pyparent.py

メッセージを送信すると、子プロセスは作成者であるためcat std_out &完了します。メッセージを受信すると完了し、この時点でパイプであるパイプを閉じてサブプロセスを完了できます。quitstd_outchild_original.pyquitstdoutstd_outclosecat

cat > std_inprocess で始まるパイプから読み取っているため、まだ完了しておらず、parent.pyプロセスparent.pyはパイプを閉じるのに気を遣っていません。その場合cat > stdin_in、プロセス全体がそれchild.py自体で完了し、パイプやセクションを閉じる必要はありません。killing(高速化による競合状態のため、UNIXで子供以外のプロセスを終了することは常に潜在的なセキュリティホールです。)リサイクルが必要です。

パイプの右端のプロセスは通常、標準入力を読み取った後にのみ完了しますが、そのプロセス()を閉じないため、child.stdin暗黙的に子プロセスに「待ってください。なので終了しても大丈夫です。

つまり、parent.py行動を合理的にしてください。

from __future__ import print_function
from subprocess import Popen, PIPE
import os

child = Popen('./child.py', stdin=PIPE, stdout=PIPE)

for letter in 'abcde':
    print('Parent writes to child: ', letter)
    child.stdin.write(letter+'\n')
    child.stdin.flush()
    response = child.stdout.readline()
    print('Response from the child:', response)
    assert response.rstrip() == letter.upper(), 'Wrong response'

child.stdin.write('quit\n')
child.stdin.flush()
child.stdin.close()
print('Waiting for the child to terminate...')
child.wait()
print('Done!')

child.pyこんなに簡単にできます。

#!/bin/sh
cat std_out &
cat > std_in
wait #basically to assert that cat std_out has finished at this point

child.stdin(fd dup呼び出しを削除した理由は、そうでなければ両方の呼び出しと冗長呼び出しの両方を閉じる必要があるためですchild_stdin)。

parent.pygnuはライン指向の方法で実行されるため、gnuはバッファcatリングされず(mikeservが指摘したように)ライン指向のchild_original.py方法で動作するため、実際にライン全体がバッファリングされます。


猫について注意すべき点:catUnbufferedはgnuがバッファを使用するので、おそらく最も幸運な用語ではないでしょう。この機能が実行しないことは、(stdioとは異なり)コンテンツを書き込む前にバッファ全体を埋めることです。デフォルトでは、特定のサイズ(バッファサイズ)の読み取り要求をオペレーティングシステムに送信し、ライン全体またはバッファ全体を取得するのを待たずに受信したすべての内容を書き込みます。 (読書(2)怠惰に要求されたバッファ全体ではなく、現在提供できるものだけを提供できます。 )

(ソースコードは以下から確認できます。http://git.savannah.gnu.org/cgit/coreutils.git/tree/src/cat.c; safe_read(通常の代わりに使用read)はgnulibサブモジュールにあり、非常に単純なラッパーです。読書(2)抽象化されましたEINTR(manページを参照)。

答え2

を使用すると、sed入力は常にラインバッファから読み取られ、wこのコマンドを使用して出力の各ラインを明示的にフラッシュできます。たとえば、

(       cd /tmp; c=
        mkfifo i o
        dd  bs=1    <o&
        sed -n w\ o <i&
        while   sleep 1
        do      [ -z "$c" ] && rm [io]
                [ "$c" = 5 ]   && exit
                date "+%S:%t$((c+=1))"
        done|   tee i
)

44: 1
44: 1
45: 2
45: 2
46: 3
46: 3
47: 4
47: 4
48: 5
48: 5
30+0 records in
30+0 records out
30 bytes (30 B) copied, 6.15077 s, 0.0 kB/s

...どこtee (これは非ブロックバッファの指定)sed出力を端子とパイプに同時に書き込みますi。 1行ずつ読み込み、読み取ったsed各行をすぐにutパイプに書き込みます。 utパイプは一度に1バイトを読み取り、utパイプと標準出力を共有するため、同時に出力を端末に書き込みます。明示的なラインバッファリングがないと、これは発生しません。以下は同じ実行ですが、riteコマンドはありません。iwoddoteesedw

(       cd /tmp; c=
        mkfifo i o
        dd  bs=1    <o&
        sed ''  >o  <i&
        while   sleep 1
        do      [ -z "$c" ] && rm [io]
                [ "$c" = 5 ]   && exit
                date "+%S:%t$((c+=1))"
        done|   tee i
)

48: 1
49: 2
50: 3
51: 4
52: 5
48: 1
49: 2
50: 3
51: 4
52: 5
30+0 records in
30+0 records out
30 bytes (30 B) copied, 6.15348 s, 0.0 kB/s

この場合、入力が閉じられるまでsedブロックバッファには何も書き込まれずdd、このとき出力をフラッシュして終了します。実際には、ddパイプの最後に書かれた診断が示すように、どちらの場合も、作成者が終了したときに終了します。

それにもかかわらず...

(       cd /tmp; c=
        mkfifo i o
        dd  bs=1    <o&
        cat >o      <i&
        while   sleep 1
        do      [ -z "$c" ] && rm [io]
                [ "$c" = 5 ]   && exit
                date "+%S:%t$((c+=1))"
        done|   tee i
)

40: 1
40: 1
41: 2
41: 2
42: 3
42: 3
43: 4
43: 4
44: 5
44: 5
30+0 records in
30+0 records out
30 bytes (30 B) copied, 6.14734 s, 0.0 kB/s

今私はcatGNUバージョンです - GNUcat (オプションなしで呼び出された場合)絶対ブロックバッファ。 GNUも使用している場合、cat問題はリレーにあるのではなくJavaプログラムにあることは明らかです。しかし、いいえGNUを使用すると、cat出力をバッファリングできます。でも運がいい、ただ一つだけだPOSIX 仕様オプションcat、これは-unbuffered出力に適用されます。試してみてください。

私はあなたの材料を見て、しばらくそれを持って遊んだ後、あなたの問題がデッドロックにあることを確信しています。cat最終的に、入力がそこで中断され、JVMプロセスも誰かと話すのを待っている場合、おそらく何も起こらないでしょう。だから私はこう書いています:

#!/bin/sh
die()   for io  in  i o e
        do      rm "$io"
                kill -9 "$(($io))"
        done    2>/dev/null
io()    while   eval "exec $((fd+=1))>&-"
        do      [ "$fd" = 9 ] &&
                { cat; kill -1 0; }
        done
cd /tmp; fd=1
mkfifo   i o e
{   io <o >&4 & o=$!
    io <e >&5 & e=$!
    io >i <&3 & i=$!
}   3<&0  4>&1  5>&2
trap "die; exit 0" 1
echo; wait

残念ながら、戻りコードの処理は少し粗雑です。ただし、より多くのタスクを実行すると、これを確実に実行できます。とにかく背景はご覧のようにみんなcat私はこれがすべての場合にそれらすべてを殺す鎖をwait始めなければならないと思います。cat

答え3

Bashでは、次のことを試すことができます。

forward() { while read line; do echo "$line"; done; } 
forward </tmp/out & 
forward </tmp/err >&2 &
forward >/tmp/in
wait

その後、実行スクリプトを使用してくださいstdbuf -i0 -oL

このforward関数はデフォルトでpipesrcとdestがstdinとstdoutにデフォルト設定され、明示的なフラッシュなしでPythonコードのメソッドです。うまくいけばstdbuf、これはトリックを行います。

すでにパフォーマンスに興味があり、それをCコードに入れたい場合は、Cコードに入れてください。私はstdbufフレンドリーな選択肢に慣れていませんが、以下は(ほぼ)ting用catのC ++シングルライナーです。catstdinstdout

#include <iostream>
using namespace std;
int main() { for(string line; getline(cin,line); ){ cout<<line<<'\n'; }; }

または必ずCコードが必要な場合:

#include <stdlib.h>
#include <stdio.h>
int main()
{
  const size_t N = 80;
  char *lineptr = (char*)malloc(N); //this will get realloced if a longer line is encountered
  size_t length = N;
  while(getline(&lineptr, &length, stdin) != -1){
    fputs(lineptr, stdout);
  }
  free(lineptr);
  return 0;
}

C と C++ の例はどちらもstdbufより良い設計決定のままであるため、各行の後に明示的に更新を実行しませんが、C++ の例では、各行の後に C の例を呼び出していつでも追加できますfflush(stdout)。どちらの場合も、バッファをラインバッファに事前設定してより効率的に実装できるため、「コストのかかる」C / C ++関数呼び出しを実行する必要はありません。'\n'endl

関連情報