Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Running an interactive command from within Python

Tags:

I have a script that I want to run from within Python (2.6.5) that follows the logic below:

  • Prompts the user for a password. It looks like ("Enter password: ") (*Note: Input does not echo to screen)
  • Output irrelevant information
  • Prompt the user for a response ("Blah Blah filename.txt blah blah (Y/N)?: ")

The last prompt line contains text which I need to parse (filename.txt). The response provided doesn't matter (the program could actually exit here without providing one, as long as I can parse the line).

My requirements are somewhat similar to Wrapping an interactive command line application in a Python script, but the responses there seem a bit confusing, and mine still hangs even when the OP mentions that it doesn't for him.

Through looking around, I've come to the conclusion that subprocess is the best way of doing this, but I'm having a few issues. Here is my Popen line:

p = subprocess.Popen("cmd", shell=True, stdout=subprocess.PIPE,
stderr=subprocess.STDOUT, stdin=subprocess.PIPE)
  • When I call a read() or readline() on stdout, the prompt is printer to the screen and it hangs.

  • If I call a write("password\n") for stdin, the prompt is written to the screen and it hangs. The text in write() is not written (I don't the cursor move the a new line).

  • If I call p.communicate("password\n"), same behavior as write()

I was looking for a few ideas here on the best way to input to stdin and possibly how to parse the last line in the output if your feeling generous, though I could probably figure that out eventually.

like image 471
user1521597 Avatar asked Jul 12 '12 18:07

user1521597


People also ask

How do I run an interactive session in Python?

A widely used way to run Python code is through an interactive session. To start a Python interactive session, just open a command-line or terminal and then type in python , or python3 depending on your Python installation, and then hit Enter .

How do you run a command in subprocess in Python?

Running a Command with subprocess In the first line, we import the subprocess module, which is part of the Python standard library. We then use the subprocess. run() function to execute the command. Like os.

How do you make a Python script interactive?

The interactive Python mode lets you run your script instantly via the command line without using any code editor or IDE. To run a Python script interactively, open up your command line and type python. Then hit Enter. You can then go ahead and write any Python code within the interactive mode.

How do you use interactive shell in Python?

To access the Python shell, open the terminal of your operating system and then type "python". Press the enter key and the Python shell will appear. This is the same Python executable you use to execute scripts, which comes installed by default on Mac and Unix-based operating systems.


2 Answers

If you are communicating with a program that subprocess spawns, you should check out A non-blocking read on a subprocess.PIPE in Python. I had a similar problem with my application and found using queues to be the best way to do ongoing communication with a subprocess.

As for getting values from the user, you can always use the raw_input() builtin to get responses, and for passwords, try using the getpass module to get non-echoing passwords from your user. You can then parse those responses and write them to your subprocess' stdin.

I ended up doing something akin to the following:

import sys
import subprocess
from threading import Thread

try:
    from Queue import Queue, Empty
except ImportError:
    from queue import Queue, Empty  # Python 3.x


def enqueue_output(out, queue):
    for line in iter(out.readline, b''):
        queue.put(line)
    out.close()


def getOutput(outQueue):
    outStr = ''
    try:
        while True: # Adds output from the Queue until it is empty
            outStr+=outQueue.get_nowait()

    except Empty:
        return outStr

p = subprocess.Popen("cmd", stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=False, universal_newlines=True)

outQueue = Queue()
errQueue = Queue()

outThread = Thread(target=enqueue_output, args=(p.stdout, outQueue))
errThread = Thread(target=enqueue_output, args=(p.stderr, errQueue))

outThread.daemon = True
errThread.daemon = True

outThread.start()
errThread.start()

try:
    someInput = raw_input("Input: ")
except NameError:
    someInput = input("Input: ")

p.stdin.write(someInput)
errors = getOutput(errQueue)
output = getOutput(outQueue)

Once you have the queues made and the threads started, you can loop through getting input from the user, getting errors and output from the process, and processing and displaying them to the user.

like image 65
sid16rgt Avatar answered Oct 24 '22 04:10

sid16rgt


Using threading it might be slightly overkill for simple tasks. Instead os.spawnvpe can be used. It will spawn script shell as a process. You will be able to communicate interactively with the script. In this example I passed password as an argument, obviously it is a not good idea.

import os
import sys
from getpass import unix_getpass

def cmd(cmd):
    cmd = cmd.split()
    code = os.spawnvpe(os.P_WAIT, cmd[0], cmd, os.environ)
    if code == 127:
        sys.stderr.write('{0}: command not found\n'.format(cmd[0]))
    return code

password = unix_getpass('Password: ')
cmd_run = './run.sh --password {0}'.format(password)
cmd(cmd_run)

pattern = raw_input('Pattern: ')
lines = []
with open('filename.txt', 'r') as fd:
    for line in fd:
        if pattern in line:
            lines.append(line)

# manipulate lines
like image 29
Tom Lime Avatar answered Oct 24 '22 03:10

Tom Lime