Cannot start two interactive shells using popen

2019-02-25 23:44发布

I have the following Python snippet, and cannot explain why it behaves the way it does.

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"

When I run it, I get the following output:

[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. Why is Python process stopped on second Popen call? (stopped by tty input), and how to avoid it?
  2. Why do I get logout message after echo 'works1' is finished, and how to avoid it?

2条回答
叼着烟拽天下
2楼-- · 2019-02-25 23:50

Answer to question 1:

This is because an interactive bash shell expects to be attached to a terminal (the 'controlling terminal') and acquire it in order to process job control interrupts (e.g. Control-Z). The second invocation tries to acquire the terminal but can't, so gets temporarily suspended.

Answer to question 2:

communicate writes its argument to the stdin pipe of the child process and then closes it. Bash terminates when its stdin is exhausted (it is like entering Control-D in a bash terminal session).

If you want to keep the bash child process running, then write to its stdin directly rather than use communicate as follows:

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

You do need to add the newline if you want the command to actually execute, by the way.

A solution:

If you want to run two or more interactive shells, you should setup the stdin of each shell to be a pseudo-terminal rather than a subprocess PIPE.

查看更多
Fickle 薄情
3楼-- · 2019-02-26 00:04

According to the isedev hint, I opened 2 pseudoterminals. Important thing is that master process (this python script) reads and writes on the master PTY file, while child subprocess uses slave file as stdin, stdout and stderr. Bottom part of the code is just for testing how everything works.

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"

That is it. Hope it helps someone!

查看更多
登录 后发表回答