Python CLI 脚本中的管道和提示
在Python的命令行脚本中,能不能把管道输入和TTY提示结合起来?比如,运行下面这个:
import sys
piped_text = None
if not sys.stdin.isatty():
piped_text = sys.stdin.read()
user_in = raw_input('Enter something: ')
if piped_text:
sys.stdout.write(piped_text)
sys.stdout.write(user_in + '\n')
会产生这样的输出:
~: python mrprompt.py
Enter something: something
something
~: echo foo | python mrprompt.py
Enter something: Traceback (most recent call last):
File "mrprompt.py", line 9, in <module>
user_in = raw_input('Enter something: ')
EOFError: EOF when reading a line
但我想要的输出是这样的:
~: python mrprompt.py
Enter something: something
something
~: echo foo | python mrprompt.py
Enter something: something
foo
something
换句话说,子进程能不能知道它父进程的TTY?在Python中,能不能和父进程的TTY进行交互?我在GNU Screen里使用bash(所以,读取'SSH_TTY'这个环境变量就不太适用了)。
3 个回答
在同一个程序中,可以同时接受管道输入和用户输入。
处理这个问题最简单的方法是使用 subprocess
模块。你可以在你的 Python 程序中执行其他程序。这个模块的参数可以让你设置与正在执行的程序之间的管道。
这很简单。这是标准做法。效果很好。
同样,通过程序参数接受文件也是如此。这也很简单。这是标准做法。效果很好。额外的好处是,在 Windows 的 CMD.EXE/COMMAND.COM 中,管道会自动使用临时文件。由于 DOS/Windows 不支持真正的管道,所以做更复杂的事情没有任何好处。
我知道命令行的 mysql 客户端代码在处理管道输入之前,会先接受用户输入的密码。Linux(可能还有 Mac OS X)上的解决方案可能和 Nemo 提到的类似。如果你真的想要这样的行为,并且不在乎 Windows,那就可以使用这个解决方案。(在 Windows 上,你需要使用像 wconio 这样的包。-- http://newcenturycomputers.net/projects/wconio.html)
确实可以让用户输入并接受管道输出,输出可以发送到另一个程序。这里有三个标准的文件句柄:stdin(标准输入)、stdout(标准输出)和 stderr(标准错误)。即使 stdout 被重定向到文件,你也可以写入 stderr。
你可以检查你的 stdin
输入是不是来自管道,就像使用 sys.stdin.isatty
一样。你会遇到 EOF
(文件结束符),是因为你用 stdin.read()
读取了所有的输入,然后又想用 raw_input()
再读取一些。
你不能同时从管道输入和进行交互式输入。如果你在用管道输入数据,那么就没有其他的 stdin
供 raw_input
使用。此时,唯一的 stdin
就是来自文件的那一份。
你需要做的是提供一个可选的方式,让你的脚本的某个部分可以从文件中读取输入,像这样设置一个开关:
--input=file.txt
然后为其他部分提供交互式的提示。
这个代码大致上是可以工作的:
#!/usr/bin/python
import sys
saved_stdin = sys.stdin
sys.stdin = open('/dev/tty', 'r')
result = raw_input('Enter something: ')
sys.stdout.write('Got: ' + result + '\n')
sys.stdin = saved_stdin
result2 = sys.stdin.read()
sys.stdout.write('Also got: ' + result2)
把它保存为 foo.py
,然后试试输入 echo goodbye | ./foo.py
。
当然,/dev/tty
这个东西只在 Unix 系统上存在。如果你在一个没有控制终端的进程中运行它,open()
这个函数可能会失败……