I need to run an interactive Bash instance in a separated process in Python with it's own dedicated TTY (I can't use pexpect). I used this code snippet I commonly see used in similar programs:
master, slave = pty.openpty() p = subprocess.Popen(["/bin/bash", "-i"], stdin=slave, stdout=slave, stderr=slave) os.close(slave) x = os.read(master, 1026) print x subprocess.Popen.kill(p) os.close(master)
But when I run it I get the following output:
$ ./pty_try.py bash: cannot set terminal process group (10790): Inappropriate ioctl for device bash: no job control in this shell
Strace of the run shows some errors:
... readlink("/usr/bin/python2.7", 0x7ffc8db02510, 4096) = -1 EINVAL (Invalid argument) ... ioctl(3, SNDCTL_TMR_TIMEBASE or SNDRV_TIMER_IOCTL_NEXT_DEVICE or TCGETS, 0x7ffc8db03590) = -1 ENOTTY (Inappropriate ioctl for device) ... readlink("./pty_try.py", 0x7ffc8db00610, 4096) = -1 EINVAL (Invalid argument)
The code snippet seems pretty straightforward, is Bash not getting something it needs? what could be the problem here?
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 os import sys import select import termios import tty import pty from subprocess import Popen command = 'bash' # command = 'docker run -it --rm centos /bin/bash'.split() # save original tty setting then set it to raw mode old_tty = termios.tcgetattr(sys.stdin) tty.setraw(sys.stdin.fileno()) # open pseudo-terminal to interact with subprocess master_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)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With