How to run a shell in a separate process and get a

2019-05-28 13:48发布

问题:

I have a linux application that gets an input stream from some device. That input should be directed to a shell process so that it emulates to the user a standard shell. So far, I've done it by creating a process that runs '/bin/sh' and I redirected its input, output, and stderr as follows:

import subprocess

p = subprocess.Popen(shell=False, args=['/bin/sh'], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
_make_nonblocking(p.stdout) # sets O_NONBLOCK
_make_nonblocking(p.stderr)

When I just a pass command, everything works:

p.stdin.write('pwd\n')
p.stdout.read()
'/home/dave\n'

For auto completion, I tried writing:

p.stdin.write('ls s\t')
p.stdout.read()
IOError: [Errno 11] Resource temporarily unavailable

I expect to get a list of possible completions but nothing happens until I put '\n' in stdin. (Also, there was nothing waiting in stderr).

I've looked through the telnetd code and saw the use of pty. I tried to use pty.openpty() and set slave as stdin, but that didn't work either. How should it be done?

UPDATE: I used the -i parameter as suggested. Now I have a problem that once I use Popen and press ENTER the python shell move to the background as follows:

>>> p = subprocess.Popen(shell=False, args=['/bin/sh', '-i'], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
>>> <ENTER>
[1]+ Stopped                ipython
$ 

回答1:

With bash autocompletion only works in interactive mode:

-i        If the -i option is present, the shell is interactive.

This will do proper emulation, including displaying prompt and all the usual stuff.



回答2:

Eventually to completely solve all problems I had to do several things:

  1. configure a pty device (using pty module in python).
  2. set the appropriate flags using termios (echo, signal handling, etc).
  3. start a new session (so that signals won't propagte the original process).
  4. open the pty device using an unbuffered file (passing 0 in bufsize).

This is the code that works:

def prepare():
    os.setsid() # start a new detached session
    tty.setcbreak(sys.stdin) # set standard input to cbreak mode
    old = termios.tcgetattr(sys.stdin)
    old[0] |= termios.BRKINT # transforms break to SIGINT
    old[3] &= termios.ICANON # non-canonical mode
    old[3] |= termios.ECHO | termios.ISIG # set echo and signal characters handling
    cc = old[6]
    # make input unbuffered
    cc[termios.VMIN] = 1
    cc[termios.VTIME] = 0
    termios.tcsetattr(sys.stdin, termios.TCSANOW, old)

master, slave = pty.openpty()
master = os.fdopen(master, 'rb+wb', 0) # open file in an unbuffered mode
_make_non_blocking(master)

prog = subprocess.Popen(shell=False, args=['/bin/sh', '-i'], stdin=slave, stdout=slave, stderr=subprocess.STDOUT, preexec_fn=prepare)