Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Run interactive Bash with popen and a dedicated TTY Python

Tags:

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?

like image 410
TKKS Avatar asked Jan 09 '17 07:01

TKKS


1 Answers

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) 
like image 175
Paco Avatar answered Sep 17 '22 18:09

Paco