如何在具有命名管道的程序之間轉發?
和是命名管道
/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.py
python 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() 方法相關的陷阱。- 顯然,無緩衝意味著緩衝,但我們不會等到緩衝區滿了,而是盡快刷新它。
如果你擺脫了殺戮和關閉的東西(這是不安全的,在極端情況下,你可能會在subshell 確實結束一些無辜的程序
child.py
之前死亡 ,但並非深不可測),那麼不會因為你不是而終止t 表現得像一個優秀的 UNIX 公民。(head -n 1 shutdown; kill -9 $parent) &``kill -9``child.py``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 回收)。管道右端的程序通常僅在完成讀取其標準輸入後才完成,但由於您沒有關閉該 (
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
以面向行的方式執行,gnucat
是無緩衝的(正如 mikeserv 指出的那樣)並且child_original.py
以面向行的方式執行,因此您實際上已經將整個事情都進行了行緩衝。**關於 Cat 的注意事項:**無緩衝可能不是最幸運的術語,因為 gnu
cat
確實使用了緩衝區。它不做的是在寫出東西之前嘗試讓整個緩衝區充滿(與 stdio 不同)。基本上,它向作業系統發出特定大小(其緩衝區大小)的讀取請求,並寫入它接收到的任何內容,而無需等待獲得整行或整個緩衝區。(read(2)可能很懶惰,只給你它目前可以給你的東西,而不是你要求的整個緩衝區。)(您可以在http://git.savannah.gnu.org/cgit/coreutils.git/tree/src/cat.c查看原始碼;
safe_read
(用於代替 plainread
)在gnulib
子模組中,它是一個非常簡單的包裝器圍繞read(2)抽像出來EINTR
(參見手冊頁)。