_/tmp/in
_、_/tmp/out
_、および_/tmp/err
_は名前付きパイプであり、何らかのプロセス(それぞれ読み取り、書き込み、書き込み用)によって既に作成および開かれています。
Stdinを_/tmp/in
_にパイプし、_/tmp/out
_の内容をstdoutに書き込み、_/tmp/err
_の内容が利用可能になったときにstderrに書き込む新しいプロセスを作成したいと思います。すべてが行バッファーの方法で機能するはずです。プロセスは、_/tmp/in
_を作成した他のプロセスが読み取りを停止し、_/tmp/in
_を閉じると終了する必要があります。このソリューションは、できれば追加のパッケージをインストールせずに、Ubuntuで動作するはずです。 bashスクリプトで解決したいと思います。
mikeserv は、 [〜#〜] sscce [〜#〜] がないと、私が何を望んでいるのか理解するのが難しいと指摘しました。したがって、以下はSSCCEですが、これは最小限の例であるため、かなりばかげていることに注意してください。
元の設定
親プロセスは子プロセスを起動し、子のstdinおよびstdoutを介して行ごとに通信します。実行すると、次のようになります。
_$ 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!')
_
child.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.pyというbashスクリプト(必要に応じてプロキシまたは仲介者)を配置し、_python parent.py
_を実行する前に自分でchild_original.pyを起動し、parent.pyに偽のchild.pyを呼び出させます。これで私のbashスクリプトは、parent.pyとchild_original.pyの間で転送されます。
偽のchild.py
_#!/bin/bash
parent=$$
cat std_out &
(head -n 1 shutdown; kill -9 $parent) &
cat >>std_in
_
親を実行する前にchild_original.pyを開始する_start_child.sh
_:
_#!/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.py bashスクリプトを要求します(行がバッファリングされ、child_original.pyがパイプのいずれかを閉じると終了し、追加のシャットダウンパイプは必要ありません)。
私が知っていたらよかったこと:
open
の呼び出しはすでにブロックされます。これは信じられないほど直感に反します。 名前付きパイプブロックを読み取り専用で開くのはなぜですか? も参照してください。setDamon(true)
を呼び出しますbeforeそれらを開始します)。そうしないと、全員が完了したとしても、JVMは永久にハングします。質問とは関係ありませんが、他の落とし穴は次のとおりです。 Runtime.exec()メソッド に関連する落とし穴を回避します。殺害やシャットダウンを取り除く場合(これは安全ではなく、極端な場合、child.py
が(head -n 1 shutdown; kill -9 $parent) &
サブシェルの前に死ぬとkill -9
が発生する可能性があります。無実のプロセス)の場合、child.py
はUNIX市民のように振る舞っていないため、parent.py
は終了しません。
cat std_out &
サブプロセスはquit
メッセージを送信するまでに終了します。これは、std_out
へのライターがchild_original.py
であり、quit
を受信すると終了し、その時点でstdout
を閉じます。 std_out
パイプとそのclose
により、cat
サブプロセスが終了します。
cat > std_in
は、parent.py
プロセスで発生したパイプから読み取っていて、parent.py
プロセスがわざわざそのパイプを閉じなかったため、終了していません。もしそうなら、cat > stdin_in
、したがってchild.py
全体がそれ自体で終了し、シャットダウンパイプやkilling
部分は必要ありません(UNIXであなたの子供ではないプロセスを殺すことは常に迅速なPIDリサイクルによって引き起こされた競合状態が発生した場合の潜在的なセキュリティホール)。
パイプラインの右端のプロセスは、通常、stdinの読み取りが完了した後にのみ終了しますが、それを閉じていないため(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
(fd dup呼び出しを削除したことに注意してください。そうしないと、child.stdin
とchild_stdin
の両方の重複を閉じる必要があるためです)。
parent.py
は行指向で動作するため、gnu cat
はバッファリングされておらず(mikeservが指摘しているように)、child_original.py
は行指向で動作するため、事実上すべてが行バッファ化されます。
Catに関する注記:gnu cat
はバッファーを使用するため、バッファーなしは最も幸運な用語ではない可能性があります。それがしないのは、(stdioとは異なり)書き出す前にバッファ全体をいっぱいにしようとすることです。基本的には、特定のサイズ(バッファサイズ)のOSに対して読み取り要求を行い、行全体またはバッファ全体を取得するのを待たずに、受信したものをすべて書き込みます。 ( read(2) 怠惰になる可能性があり、要求したバッファー全体ではなく、現時点で提供できるものだけを提供します。)
(ソースコードは http://git.savannah.gnu.org/cgit/coreutils.git/tree/src/cat.c ; safe_read
(の代わりに使用)で調べることができます。プレーンなread
)はgnulib
サブモジュールにあり、 read(2) の非常に単純なラッパーであり、EINTR
を抽象化します(manページを参照))。
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
パイプに同時に接続します。 sed
はi
を行ごとに読み取り、w
ritesは、読み取る各行をo
utパイプに送信します。 dd
はo
utパイプを一度に1バイト読み取り、tee
とstdoutを共有するため、両方が同時に出力を端末に書き込みます。 sed
が明示的にラインバッファを行わなかった場合、これは発生しません。これは同じ実行ですが、w
riteコマンドはありません。
( 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
今私のcat
はGNUバージョン-そしてGNUのcat
(オプションなしで呼び出された場合)neverブロックバッファ。GNU cat
も使用している場合、問題はリレーではなく、=にあることは明らかです。 Javaプログラム。ただし、notの場合は、GNU cat
の場合、出力をバッファリングする可能性があります。幸運なことに、たった1つしかありません POSIX仕様のcat
オプション、それは-u
nbuffered出力。あなたはそれを試すかもしれません。
私はあなたのことを見ていて、しばらくそれをいじった後、あなたの問題はデッドロック状態であると確信しています。最後に1つの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
sの背景allは、stdoutに空の行を書き込み、次にwait
s cat
sの1つが終了するまで、すべての場合にそれらすべてを殺すチェーンを開始する必要があると思います。
Bashでは、次のことを試すことができます。
_forward() { while read line; do echo "$line"; done; }
forward </tmp/out &
forward </tmp/err >&2 &
forward >/tmp/in
wait
_
次に、_stdbuf -i0 -oL
_を使用してスクリプトを実行します。
forward
関数は、基本的に、python srcおよびdestがデフォルトでstdinおよびstdoutになり、明示的なフラッシュがないコードからのpipe
メソッドであり、stdbuf
それをするかもしれません。
すでにパフォーマンスが心配で、Cコードにしたい場合は、Cコードに入れてください。私はstdbufに適したcat
の代替手段に精通していませんが、cat
ting stdin
からstdout
へのC++ワンライナー(ほぼ)は次のとおりです。
_#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の例でfflush(stdout)
を呼び出すことでいつでも追加できます。各行の後で、C++の例では_'\n'
_をendl
に置き換えるか、どちらの場合もバッファリングを行バッファリングに事前設定して、これらを「高価な」Cにする必要がないようにします。/C++関数呼び出し。