Run interactive Bash with popen and a dedicated TTY Python Run interactive Bash with popen and a dedicated TTY Python python python

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