簡體   English   中英

如何從另一個應用程序獲取管道輸入並在之后獲取用戶輸入?

[英]How can I take piped input from another application AND take user input after?

import sys

stdin_input = sys.stdin.read()
print(f"Info loaded from stdin: {stdin_input}")

user_input = input("User input goes here: ")

收到錯誤:

C:\>echo "hello" | python winput.py
Info loaded from stdin: "hello"

User input goes here: Traceback (most recent call last):
  File "C:\winput.py", line 6, in <module>
    user_input = input("User input goes here: ")
EOFError: EOF when reading a line

我最近了解到這是因為sys.stdin被用於 FIFO,這使得它在閱讀后關閉。

我可以根據這個問題stdin_input = sys.stdin.read()之后添加sys.stdin = open("/dev/tty")使其在 CentOS 上運行,但這不適用於 Windows。

最好不要識別操作系統並相應地為sys.stdin分配新值,我寧願動態地處理它。 有沒有一種方法可以確定在每種情況下 /dev /dev/tty tty 的等價物是什么,而不必知道/dev/tty或特定操作系統的等價物?

編輯:

sys.stdin.read()的原因是接收來自另一個應用程序的 JSON 輸入。 我還可以選擇從文件中讀取 JSON 數據,但能夠使用管道數據非常方便。 收到數據后,我想單獨獲取用戶輸入。

我目前正在解決以下問題:

if os.name == "posix":
    sys.stdin = open("/dev/tty")
elif os.name == "nt":
    sys.stdin = open("con")
else:
    raise RunTimeError(
        f"Error trying to assign to sys.stdin due to unknown os {os.name}"
    )

這在所有情況下都可以很好地工作,但仍然最好知道/dev/ttycon或操作系統的任何等效項是動態的。 如果這是不可能的,而我的解決方法是最好的解決方案,我可以接受。

所以你真正的問題是sys.stdin只能是兩件事之一:

  1. 連接到終端的類型化輸入
  2. 連接到一些不是終端的類似文件 object(實際文件,pipe,等等)

通過執行sys.stdin.read()消耗所有sys.stdin並不重要,當sys.stdin被重定向到某個文件系統 object 時,您失去了通過sys.stdin從終端讀取的能力。

實際上,我強烈建議不要嘗試這樣做。 使用argparse並接受您正在考慮通過命令行input接受的任何內容並避免整個問題(實際上,我基本上從未見過真正的生產代碼不是通過stdin / stdout交互與用戶動態交互的某種 REPL;對於在非 REPL 情況下, sys.stdin基本上總是未使用或從文件/程序中通過管道傳輸,因為像這樣編寫干凈的用戶交互代碼是一種痛苦,而且用戶必須在不出錯的情況下鍵入他們的響應是一種痛苦). 可以通過將type=argparse.FileType() add_argument stdin來處理可能來自文件或標准輸入的輸入,然后用戶可以選擇傳遞文件名或- (其中-表示“讀取自stdin"), 讓你的代碼看起來像:

parser = argparse.ArgumentParser('Program description here')
parser.add_argument('inputfile', type=argparse.FileType(), help='Description here; pass "-" to read from stdin')
parser.add_argument('-c', '--cmd', action='append', help='User commands to execute after processing input file')
args = parser.parse_args()

with args.inputfile as f:
    data = f.read()

for cmd in args.cmd:
    # Do stuff based on cmd

然后用戶可以執行以下操作:

otherprogram_that_generates_data | myprogram.py - -c 'command 1' -c 'command 2'

或者:

myprogram.py file_containing_data -c 'command 1' -c 'command 2'

或(在具有進程替換的 shell 上,如bash ,作為第一個用例的替代方案):

myprogram.py <(otherprogram_that_generates_data) -c 'command 1' -c 'command 2'

無論哪種方式,它都有效。

如果您必須這樣做,您現有的解決方案確實是唯一合理的解決方案,但您可以將其分解並使其更清晰一些,只使路徑動態化,而不是整個代碼路徑:

import contextlib
import os
import sys

TTYNAMES = {"posix": "/dev/tty", "nt": "con"}

@contextlib.contextmanager
def stdin_from_terminal():
    try:
        ttyname = TTYNAMES[os.name]
    except KeyError:
        raise OSError(f"{os.name} does not support manually reading from the terminal")
    with open(ttyname) as tty:
        sys.stdin, oldstdin = tty, sys.stdin
        try:
            yield
        finally:
            sys.stdin = oldstdin

如果在沒有連接終端的情況下運行,這可能會在open調用中死於OSError子類,例如,當在 Windows 上使用pythonw啟動時(不使用此設計的另一個原因),或者在類 UNIX 上以非終端方式啟動,但那是總比默默地犯錯好。

您只需使用它:

with stdin_from_terminal():
    user_input = input("User input goes here: ")

並且它會在退出with塊時自動恢復原始sys.stdin

由於您使用的是 Bash ,您可以通過使用進程替換來避免此問題,它類似於 pipe,但通過臨時文件名參數而不是通過標准輸入傳遞。

那看起來像:

winput.py <(another-application)

然后在您的 Python 腳本中,接收參數並相應地處理它:

import json
import sys

with open(sys.argv[1]) as f:
    d = json.load(f)
print(d)

user_input = input("User input goes here: ")
print('User input:', user_input)

sys.argv僅用於演示。在真實腳本中我會使用argparse 。)

示例運行:

$ tmp.py <(echo '{"someKey": "someValue"}')
{'someKey': 'someValue'}
User input goes here: 6
User input: 6

另一個巨大的優勢是它可以與實際文件名無縫協作,例如:

$ cat test.json
{"foo": "bar"}
$ tmp.py test.json
{'foo': 'bar'}
User input goes here: x
User input: x

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM