명명된 파이프를 사용하여 프로세스 간에 전달하는 방법은 무엇입니까?

명명된 파이프를 사용하여 프로세스 간에 전달하는 방법은 무엇입니까?

/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을 통해 한 줄씩 통신합니다. 이것을 실행하면 다음과 같은 결과를 얻습니다.

$ 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. 한 줄씩 읽고 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

관련 정보