Run interactive Bash with popen and a dedicated TTY Python

匿名 (未验证) 提交于 2019-12-03 08:28:06

问题:

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?

回答1:

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()  # 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)  # restore tty settings back termios.tcsetattr(sys.stdin, termios.TCSADRAIN, old_tty) 


回答2:

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 


回答3:

you will have to pass input to read and then print x.



标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!