Shell

如何在具有命名管道的程序之間轉發?

  • July 20, 2015

和是命名管道/tmp/in,已經由某個程序創建/tmp/out/tmp/err打開(分別用於讀取、寫入和寫入)。

我想創建一個新程序,將其 stdin 輸入/tmp/in,並將 的內容寫入/tmp/out其 stdout ,並將 的內容/tmp/err寫入其 stderr ,因為它們可用。一切都應該以行緩衝的方式工作。/tmp/in當創建的另一個程序停止讀取並關閉時,該程序應該退出/tmp/in。該解決方案應該可以在 Ubuntu 上執行,最好不要安裝任何額外的軟體包。我想在 bash 腳本中解決它。


mikeserv指出,如果沒有SSCCE,就很難理解我想要什麼。所以,下面是一個 SSCCE,但請記住,它是一個最小的範例,所以它非常愚蠢。

原來的設置

一個父程序啟動一個子程序,並通過子程序的標準輸入和標準輸出逐行與其通信。如果我執行它,我會得到:

$ 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 腳本(代理或中間人),在執行之前自己啟動 child_original.pypython parent.py並讓 parent.py 呼叫現在是我的 bash 腳本的假 child.py,在 parent.py 和 child_original.py 之間轉發。

假孩子.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
$ 

這種駭人聽聞的解決方案有效。據我所知,它不滿足行緩衝要求,並且有一個額外的關閉 fifo 來通知 start_child.sh child_original.py 已關閉管道並且 start_child.sh 可以安全退出。


該問題要求改進的假 child.py bash 腳本,滿足要求(行緩衝,當 child_original.py 關閉任何管道時退出,不需要額外的關閉管道)。



我希望我知道的事情:

  • 如果使用高級 API 將 fifo 作為文件打開,則必須為 read 和 write 打開它*,*否則對open已經阻塞的呼叫。這是令人難以置信的反直覺。另請參閱為什麼以只讀方式打開命名管道塊?
  • 實際上,我的父程序是一個 Java 應用程序。如果您使用 Java 的外部程序,請從守護執行緒讀取外部程序的標準輸出和標準錯誤(在啟動它們**之前呼叫setDamon(true)這些執行緒)。否則,即使每個人都完成了,JVM 也會永遠掛起。儘管與問題無關,但其他陷阱包括:繞開與 Runtime.exec() 方法相關的陷阱
  • 顯然,無緩衝意味著緩衝,但我們不會等到緩衝區滿了,而是盡快刷新它。

如果你擺脫了殺戮和關閉的東西(這是不安全的,在極端情況下,你可能會在subshel​​l 確實結束一些無辜的程序child.py之前死亡 ,但並非深不可測),那麼不會因為你不是而終止t 表現得像一個優秀的 UNIX 公民。(head -n 1 shutdown; kill -9 $parent) &``kill -9``child.py``parent.py

cat std_out &子流程將在您發送消息時完成,quit因為寫入器std_outchild_original.py,它在接收quit到它的那一刻完成它關閉它的stdout,這是std_out管道,這close將使cat子流程完成。

沒有完成,cat > std_in因為它正在從源自parent.py程序的管道讀取,並且parent.py程序沒有費心關閉該管道。如果確實如此,cat > stdin_in那麼整個child.py過程將自行完成,並且您不需要關閉管道或killing部分(如果由於快速導致的競爭條件,在 UNIX 上殺死一個不是您的孩子的程序總是一個潛在的安全漏洞)應該進行 PID 回收)。

管道右端的程序通常僅在完成讀取其標準輸入後才完成,但由於您沒有關閉該 ( 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.stdinchild_stdin副本)。

由於parent.py以面向行的方式執行,gnucat是無緩衝的(正如 mikeserv 指出的那樣)並且child_original.py以面向行的方式執行,因此您實際上已經將整個事情都進行了行緩衝。


**關於 Cat 的注意事項:**無緩衝可能不是最幸運的術語,因為 gnucat確實使用了緩衝區。它不做的是在寫出東西之前嘗試讓整個緩衝區充滿(與 stdio 不同)。基本上,它向作業系統發出特定大小(其緩衝區大小)的讀取請求,並寫入它接收到的任何內容,而無需等待獲得整行或整個緩衝區。(read(2)可能很懶惰​​,只給你它目前可以給你的東西,而不是你要求的整個緩衝區。)

(您可以在http://git.savannah.gnu.org/cgit/coreutils.git/tree/src/cat.c查看原始碼;safe_read(用於代替 plain read)在gnulib子模組中,它是一個非常簡單的包裝器圍繞read(2)抽像出來EINTR(參見手冊頁)。

引用自:https://unix.stackexchange.com/questions/215414