我想从Python调用一个程序,并使其相信即使Python的进程stdout附加到管道,它的stdout
也是一个tty。所以我使用pty.spawn
函数来实现这一点,可以通过以下方式验证:
$ python -c "import sys; from subprocess import call; call(sys.argv[1:])" python -c "import sys; print sys.stdout.isatty()" | cat
False
$ python -c "import sys; import pty; pty.spawn(sys.argv[1:])" python -c "import sys; print sys.stdout.isatty()" | cat
True
(我们看到在第二个命令中我们已经实现了我们的目标,即产生的进程被欺骗认为它的标准输出是tty。)
但问题是,如果我们使用pty.spawn
,那么它的输入不会被回显,而是被重定向到主要的标准输出。这可以通过以下命令看到:
$ python -c "import sys; import pty; pty.spawn(sys.argv[1:])" cat > out.txt
$ # Typed "hello" in input, but that is not echoed (use ^D to exit). It is redirected to output.txt
$ cat out.txt
hello
hello
(但是当我们使用subprocess.call
$ python -c "import sys; from subprocess import call; call(sys.argv[1:])" cat > out1.txt
hello
$ cat out1.txt
hello
因为它的stdin和stdout正确地附加到主服务器上。)
我无法找到一种方法,以便Python调用一个程序,它将stdout
视为tty(类似于pty.spawn
),但其输入正确回显(类似于{{ 1}})。有什么想法吗?
答案 0 :(得分:5)
您正在创建一个stdout连接到文件的终端,因此终端执行的正常回显将被发送到文件而不是屏幕。
我不确定spawn
是否可以像这样直接使用:pty
库提供pty.fork()
来创建子进程并返回stdin / stdout的文件描述符。但是你需要更多的代码才能使用它。
要克服当前与spawn
的问题,还有两个简单的选择:
选项1:如果您关心的是将生成的命令的输出发送到文件,那么您可以(我更喜欢命名管道和here
文件用于python one-衬套):
$ python <(cat << EOF
import sys
import pty
print 'start to stdout only'
pty.spawn(sys.argv[1:])
print 'complete to stdout only'
EOF
) bash -c 'cat > out.txt'
在运行时会如下所示:
start to stdout only
hello
complete to stdout only
表示输入(我键入的hello)和print语句的结果将进入屏幕。 out.txt的内容将是:
$ cat out.txt
hello
即只是你输入的内容。
选项2:另一方面,如果你想让out文件包含生成的命令输出周围的python输出,那么你需要更复杂的东西,比如:
python <(cat << EOF
import sys
import pty
import os
old_stdout = sys.stdout
sys.stdout = myfdout = os.fdopen(4,"w")
print 'start to out file only'
myfdout.flush()
pty.spawn(sys.argv[1:])
print 'complete to out file only'
sys.stdout = old_stdout
EOF
) bash -c 'cat >&4' 4>out.txt
只有在运行时才输出到终端(即你输入的内容):
hello
但out文件将包含:
$ cat out.txt
start to out file only
hello
complete to out file only
后台: python pty
库功能强大:它创建了一个连接到python的终端设备,处理stdout和stdin。我想大多数使用它将使用pty.fork()
调用,以便真正的stdin / stdout不受影响。
但是在你的情况下,在你的shell中,你将python进程的stdout重定向到一个文件。因此,生成的pty也将其stdout附加到文件中,因此正在重定向将stdin回送到stdout的正常操作。常规标准输出(屏幕)仍然存在,但新pty没有使用。
上面选项1 的关键区别是将stdout的重定向移到pty.spawn
调用内的某处,以便创建的pty仍然与实际终端有明确的连接stdout(用于在键入时尝试回显stdin)
选项2 的区别在于在任意文件描述符(即文件描述符4)上创建第二个通道,并使用它代替stdout,一旦进入python并创建了生成进程(即将生成的进程的stdout重定向到同一文件描述符)
这两个区别都阻止了pty.spawn
创建的pty从其stdout更改或与真实终端断开连接的pty。这允许stdin的回显正常工作。
有些软件包使用pty
库并为您提供更多控制权,但您会发现其中大部分都使用pty.fork()
(有趣的是我还没找到一个实际使用{{1} }})
编辑以下是使用pty.fork()的示例:
pty.spawn
编辑此question为import sys
import pty
import os
import select
import time
import tty
import termios
print 'start'
try:
pid, fd = pty.fork()
print 'forked'
except OSError as e:
print e
if pid == pty.CHILD:
cmd = sys.argv[1]
args = sys.argv[1:]
print cmd, args
os.execvp(cmd,args)
else:
tty.setraw(fd, termios.TCSANOW)
try:
child_file = os.fdopen(fd,'rw')
read_list = [sys.stdin, child_file]
while read_list:
ready = select.select(read_list, [], [], 0.1)[0]
if not ready and len(read_list) < 2:
break
elif not ready:
time.sleep(1)
else:
for file in ready:
try:
line = file.readline()
except IOError as e:
print "Ignoring: ", e
line = None
if not line:
read_list.remove(file)
else:
if file == sys.stdin:
os.write(fd,line)
else:
print "from child:", line
except KeyboardInterrupt:
pass
提供了一些良好的链接
更新:应该在代码中添加一些注释
pty.fork()
示例的工作原理:
当解释器执行对pty.fork()
的调用时,处理分为两部分:现在有两个线程似乎都刚刚执行了pty.fork()
调用。
一个线程是您最初的线程(父线程),一个是新线程(子线程)。
在父级中,pty.fork()
和pid
设置为子级的进程ID,文件描述符连接到子级的stdin和stdout:在父级中,当您从fd
读到你正在阅读写给孩子们的内容;当你写信给fd
时,你正在给孩子们写信。所以现在,在父级中,我们可以通过其stdout / stdin与其他线程进行通信。
在孩子中,fd
设置为0且未设置pid
。如果我们想与父线程交谈,我们可以通过stdin / stdout读取和写入,知道父母可以而且应该对此做些什么。
这两个线程将从此时开始执行相同的代码,但我们可以根据fd
中的值判断我们是在父线程还是子线程中。如果我们想在子线程和父线程中执行不同的操作,那么我们只需要一个条件语句,将子进程发送到一个代码路径,将父进程发送到不同的代码路径。这就是这条线的作用:
pid
在孩子中,我们只想在新的pty中生成新命令。使用if pid == pty.CHILD:
#child thread will execute this code
....
else
#parent thread will execute this code
...
是因为我们将使用此方法更多地控制pty作为终端,但基本上与os.execvp
fd pty.spawn()'. This means the child stdin/stdout are now connected to the command you wanted via a pty. IMmportantly, any input or output from the command (or the pty for that matter) will be available to the parent thread by reading from
fd`
所以现在,在父级中,我们需要通过读写. And the parent can write to the command via pty by writing to
将真正的stdin / stdout连接到子stdin / stdout。这就是父代码现在做的事情(fd
部分)。在真正的stdin上出现的任何数据都写到else
。从fd
(由父项)读取的任何数据都将写入stdout。所以父线程现在唯一要做的就是在真正的stdin / stdout和fd之间进行代理。如果你想以编程方式对命令的输入和输出做一些事情,那就是你要做的事情。
父母身上发生的另一件事是这个电话:
fd
这是告诉孩子的pty停止回声的一种方法。
这解决了您最初遇到的问题: - 您的本地终端仅连接到父线程 - 正常回显就绪(即在您的输入传递到流程之前) - 可以重定向该进程的标准输出 - 无论你使用终端stdout做什么都不会影响子进程的stdin / stdout - 已告知子进程不对其标准输入进行本地回显
这似乎有很多解释 - 如果有人为了清晰起见有任何修改?