无法使用popen启动两个交互式shell

2 投票
2 回答
1507 浏览
提问于 2025-04-17 16:17

我有一段Python代码,无法解释它为什么会这样运行。

import subprocess  
bash1 = subprocess.Popen(["/bin/bash","-l", "-i"], stdin=subprocess.PIPE)  
print "Checkpoint 1"  
bash2 = subprocess.Popen(["/bin/bash","-l", "-i"], stdin=subprocess.PIPE)  
print "Checkpoint 2"  
bash1.communicate("echo 'works1'")  
bash2.communicate("echo 'works2'")  
print "OK"

当我运行这段代码时,得到的输出是:

[user@localhost ~]$ python test.py  
Checkpoint 1  
Checkpoint 2  
[1]+  Stopped                 python test.py
[user@localhost ~]$ [user@localhost ~]$ echo 'works1'  
works1  
[user@localhost ~]$ logout  
[user@localhost ~]$ fg    
python test.py  
[user@localhost ~]$ echo 'works2'  
works2  
[user@localhost ~]$ logout 
OK  
[user@localhost ~]$
  1. 为什么在第二次调用Popen时,Python进程会停止?(是因为tty输入导致的),我该如何避免这种情况?
  2. 为什么在执行完echo 'works1'后会收到注销消息,我该如何避免这种情况?

2 个回答

2

根据isedev的提示,我打开了两个伪终端。重要的是,主进程(这个Python脚本)在主伪终端文件上进行读写,而子进程则使用从伪终端文件作为输入、输出和错误输出。代码的底部部分只是用来测试一切是如何工作的。

import subprocess
import os
import pty
import select
import time

# according to> http://fleckenzwerg2000.blogspot.com/2011/10/running-and-controlling-gnu-screen-from.html
(master1, slave1) = pty.openpty()
bash1 = subprocess.Popen(["bash", "-l", "-i"], stdin=slave1, stdout=slave1, stderr=slave1)
(master2, slave2) = pty.openpty()
bash2 = subprocess.Popen(["bash", "-l", "-i"], stdin=slave2, stdout=slave2, stderr=slave2)
data = "echo 'bla'\n"

## taken from> http://stackoverflow.com/questions/14564904/how-to-send-tab-key-to-python-subprocesss-stdin
def write_all(masterPTY, data):
    """Successively write all of data into a file-descriptor."""
    while data:
        chars_written = os.write(masterPTY, data)
        data = data[chars_written:]
    return data

def read_all(masterPTY):
    r,w,x = select.select([masterPTY], [], [], 10)
    if r:
        data = os.read(masterPTY, 1024)
        return data


write_all(master1, "echo 'bla1'\n")
write_all(master2, "echo 'bla2'\n")
time.sleep(1)
print read_all(master1)
write_all(master1, "echo 'bla1'\n")
time.sleep(1)

print read_all(master2)

time.sleep(1)

os.close(master1)
os.close(slave1)
os.close(master2)
os.close(slave2)

bash1.terminate()
bash2.terminate()

print "OK"

就是这样。希望能对某些人有所帮助!

3

回答问题1:

这是因为一个交互式的bash终端需要连接到一个终端(也就是“控制终端”),这样它才能处理一些控制命令,比如按下Control-Z。第二次调用的时候,它想要连接这个终端,但没能成功,所以就暂时被挂起了。

回答问题2:

communicate这个方法会把你给它的内容写入到子进程的标准输入(stdin)里,然后把这个输入关闭。当bash的标准输入用完了,它就会结束(这就像在bash终端里按下Control-D)。

如果你想让bash的子进程继续运行,就要直接写入它的标准输入,而不是用communicate,可以这样做:

bash1.stdin.write("echo 'works1'\n")

顺便说一下,如果你想让命令真正执行,就需要加上换行符。

解决方案:

如果你想运行两个或更多的交互式终端,你应该把每个终端的标准输入设置为一个伪终端,而不是一个子进程的管道。

撰写回答