我想从Python调用一个程序,并使其相信它的stdout
是一个tty,即使Python的进程标准输出连接到一个pipe道。 所以我使用了pty.spawn
函数来实现,可以通过以下方法来validation:
$ 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
那么它的input不会被回显,而是被redirect到主站的stdout。 这可以通过以下命令看到:
$ 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
因为它的标准input和标准输出正确地连接到主站。)
我无法find一种方式,以便程序被Python调用,它将stdout
视为tty(类似于pty.spawn
),但其input正确回显(类似于pty.spawn
)。 有任何想法吗?
你正在创建一个stdout连接到一个文件的终端,所以终端所做的正常的回声被发送到文件而不是屏幕。
我不确定这个spawn
是否可以像这样直接使用: pty
库提供了pty.fork()
来创建一个子进程,并为stdin / stdout返回一个文件描述符。 但是你需要更多的代码来使用它。
为了克服目前你遇到的问题,继承人有两个简单的选择:
选项1:如果你所关心的是将产生的命令的输出发送到一个文件,那么你可以做(我喜欢命名的管道和here
文件的Python单行):
$ 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)和打印语句的结果正在屏幕上。 out.txt的内容是:
$ cat out.txt hello
那就是,只有你输入的内容。
选项2:另一方面,如果你希望out文件在spawned命令输出中包含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进程的标准输出重定向到了一个文件中。 因此得到的pty也有它的标准输出附加到文件,所以正常的行动回声stdin回stdout被重定向。 常规stdout(屏幕)仍然在位,但没有被新的pty使用。
上面的选项1的主要区别在于将stdout的重定向移动到pty.spawn
调用中的某个pty.spawn
,以便创建的pty仍然与实际的终端stdout有明确的连接(因为当您尝试在输入stdin时回显stdin )
选项2的区别在于在任意文件描述符(即文件描述符4)上创建第二个通道,一旦在python中创建了衍生进程(例如,重定向生成的stdout处理到相同的文件描述符)
这两个差别都会阻止pty.spawn
创建的pty从标准输出更改或从实际终端断开连接。 这样可以使stdin的回显正常工作。
有一些包使用pty
库,给你更多的控制,但你会发现大部分这些使用pty.fork()
(有趣的是,我还没有找到一个,但实际上使用pty.spawn
)
编辑这里使用pty.fork()的例子:
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
和fd
被设置为子进程ID,并且是一个文件描述符,用来连接子进程stdin和stdout:在父进程中,当你从fd
读取的时候,你正在读什么已经写入子进程标准输出; 当你写信给你正在写信给孩子们的标准。 所以现在,在父类中,我们有一种通过stdout / stdin与其他线程进行通信的方式。
在孩子中, pid
设置为0, fd
没有设置。 如果我们想和父线程交谈,我们可以读写标准输入/标准输出,知道父母可以并且应该对此做些什么。
这两个线程将从这一点执行相同的代码,但是我们可以根据pid
的值来判断我们是在父线程还是在子线程中。 如果我们想要在子线程和父线程中做不同的事情,那么我们只需要一个条件语句来将子代码向下传递给一个代码路径,而父代向下传递不同的代码路径。 那是什么这行:
if pid == pty.CHILD: #child thread will execute this code .... else #parent thread will execute this code ...
在孩子中,我们只是想在新的pty中产生新的命令。 os.execvp
被使用,因为我们将有更多的控制作为这种方法终端的pty,但本质上它与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
相同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
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
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 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
. And the parent can write to the command via pty by writing to
. And the parent can write to the command via pty by writing to
fd` . And the parent can write to the command via pty by writing to
所以现在,在父类中,我们需要通过读写fd
来将真正的stdin / stdout连接到子标准输入/标准输出。 那就是现在的父代码( else
部分)。 任何出现在真实stdin上的数据都被写出到fd
。 从fd
读取的任何数据(由父级)被写入stdout。 所以父线程现在唯一做的就是在真正的stdin / stdout和fd之间进行代理。 如果你想用命令的输入和输出来做一些事情,那么这就是你要做的地方。
在父母身上发生的唯一的其他事情是这个电话:
tty.setraw(fd, termios.TCSANOW)
这是告诉孩子停止回音的方法之一。
这解决了你原来的问题: – 你的本地终端只连接到父线程 – 正常的回显到位(即在你的输入被传入进程之前) – 进程的标准输出可以被重定向 – 无论你做你的终端标准输出没有影响的标准输入/标准输出的子进程 – 子进程已被告知不做本地回显标准输入
这似乎是很多的解释 – 如果有人有任何清晰的编辑?