Calling bash from python subprocess maintaining context, returning results and printing to screen

34 Views Asked by At

I have a function that sends a statement to bash. It prints the output in real time to the console and also returns the value. It works flawlessly and looks like this:

def call_in_bash(statement): 
  process = subprocess.Popen(['bash','-c',statement],stdout=subprocess.PIPE, universal_newlines=True, bufsize=1)
  buffer = io.StringIO()
  selector = selectors.DefaultSelector()
  selector.register(process.stdout, selectors.EVENT_READ)
  line = ''
  while process.poll() is None:
    events = selector.select()
    for key, mask in events:
      line = key.fileobj.readline()
      buffer.write(line)
      sys.stdout.write(line)

  selector.close()
  output = buffer.getvalue()
  buffer.close()
  return output

However, I'd like to make this a class with a constructor and a destructor so subsequent calls can keep the context until I destroy the object. That is, I can call_in_bash("cd folder") and then call_in_bash("do something") in that folder. To do so, I've tried to do something like this:

def __init__(self):
  self.process: Popen(['bash'], stdin=subprocess.PIPE, stdout=subprocess.PIPE ...)

def call_to_bash(self, statement):
  self.process.stdin.write(statement + "\n")
  self.process.stdin.flush()
  
  # from here on same as before

It locks and never finishes no matter the command. I don't know what big of a difference it makes if I call popen(['bash'] and then put stuff in its stdin instead of calling bash -c statement. In my mind they're pretty much the same thing but I'm clearly missing something important here. I have read lots of other questions and they address either keeping the context, returning the value or printing in real time, but never all three things at once.

1

There are 1 best solutions below

0
Diego Torres Milano On

You can do something like this to control and interactive shell with pexpect:

#! /usr/bin/env python3

import pexpect
import sys

PROMPT = r"bash-5.2\$ "

c = pexpect.spawn("bash -i", encoding="utf-8")
c.logfile = sys.stdout
c.expect(PROMPT)
c.sendline("cd /tmp")
c.expect(PROMPT)
c.sendline("ls -l")
c.expect(PROMPT)
c.close()

Adapt it to your needs.