Run interactive Bash with popen and a dedicated TTY Python
This is a solution to run an interactive command in subprocess. It uses pseudo-terminal to make stdout non-blocking(also some command needs a tty device, eg. bash). it uses select to handle input and ouput to the subprocess.
#!/usr/bin/env python# -*- coding: utf-8 -*-import osimport sysimport selectimport termiosimport ttyimport ptyfrom subprocess import Popencommand = 'bash'# command = 'docker run -it --rm centos /bin/bash'.split()# save original tty setting then set it to raw modeold_tty = termios.tcgetattr(sys.stdin)tty.setraw(sys.stdin.fileno())# open pseudo-terminal to interact with subprocessmaster_fd, slave_fd = pty.openpty()try: # use os.setsid() make it run in a new process group, or bash job control will not be enabled p = Popen(command, preexec_fn=os.setsid, stdin=slave_fd, stdout=slave_fd, stderr=slave_fd, universal_newlines=True) while p.poll() is None: r, w, e = select.select([sys.stdin, master_fd], [], []) if sys.stdin in r: d = os.read(sys.stdin.fileno(), 10240) os.write(master_fd, d) elif master_fd in r: o = os.read(master_fd, 10240) if o: os.write(sys.stdout.fileno(), o)finally: # restore tty settings back termios.tcsetattr(sys.stdin, termios.TCSADRAIN, old_tty)
This is the solution that worked for me at the end (as suggested by qarma) :
libc = ctypes.CDLL('libc.so.6')master, slave = pty.openpty()p = subprocess.Popen(["/bin/bash", "-i"], preexec_fn=libc.setsid, stdin=slave, stdout=slave, stderr=slave)os.close(slave)... do stuff here ...x = os.read(master, 1026)print x