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/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/tty
或con
,或者OS的等效物是动态的。如果这不可能,而我的变通方法是最好的解决方案,我可以接受。
由于您使用的是Bash,因此可以通过使用进程替换来避免此问题,进程替换类似于管道,但通过临时文件名参数而不是通过stdin传递。
这看起来像:
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
所以真正的问题是sys.stdin
只能是两件事中的一件:
- 连接到来自终端的键入输入
- 连接到某个类似文件的对象,该对象是而不是终端(实际文件、管道等)
通过执行sys.stdin.read()
消耗了所有sys.stdin
并不重要,当sys.stdin
重定向到某个文件系统对象时,您失去了通过sys.stdin
从终端读取的能力。
在实践中,我强烈建议不要尝试这样做。使用argparse
,从命令行通过input
接受您考虑接受的任何内容,避免整个问题(在实践中,我基本上从来没有看到过真正的生产代码不是某种REPL,而是通过stdin
/stdout
交互与用户动态交互;对于非REPL的情况,sys.stdin
基本上总是未使用或从文件/程序中通过管道传输,因为编写这样干净的用户交互代码是一种痛苦,而用户必须键入他们的响应而不犯错误也是一种痛苦takes)。文件或stdin
的输入可以通过将type=argparse.FileType()
传递给有问题的add_argument
调用来处理,然后用户可以选择传递文件名或-
(其中-
的意思是"从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: ")
并且当CCD_ 32块退出时它将自动恢复原始的CCD_。