Python subprocess allow for user interaction - python

I am attempting to run a number of student FORTRAN programs from a python script. These programs are not written in any particular order and often rely on the simple FORTRAN read(*,*) command. A simple program could be:
program main
implicit none
real :: p,t
write(*,*)'Enter p'
read(*,*)p
write(*,*)'Enter t'
read(*,*)t
write(*,*)p,t
end program main
This code pauses and allows the user to enter in certain information based on the prompt. I would like similar feature by using the subprocess Popen command. The script will not know what the inputs are before running, or if they even need to happen.
Currently, for programs with no necessary input the following script works:
p = sub.Popen('./file',stdout=sub.PIPE,stderr=sub.PIPE,stdin=sub.PIPE,shell=True)
output,error = p.communicate()
Is there any way to allow the script runner to enter the data in the terminal as the program is being run?

It looks like you want to use pexpect:
import pexpect
child = pexpect.spawn('student_program')
while child.expect('Enter (\w+)\r\n', pexpect.EOF) == 0:
if child.match[1] == 'p':
child.sendline('3.14159')
To pass interactive control of the program to the user, use child.interact().

Related

Interactively communicating with a FORTRAN shell program

DAOPHOT is a FORTRAN-written software for performing astronomy tasks in images. A typical flow of its usage is:
Open a terminal (gnome-terminal in my case) and run ./daophot. I'm now within DAOPHOT's shell.
Prompts the user for a command, let's say ATTACH to input an image file. DAOPHOT runs and prompts the user again for more commands.
User gives another command, let's say PHOTOMETRY. DAOPHOT runs and prompts the user again.
For every command the user gives, DAOPHOT runs and prompts again and again until exit is typed. For my case, I have three specific commands that will run one after another, without variation (ATTACH, PHOTOMETRY and PSF, with the latter maybe run more than once).
Right now I'm simply trying to ATTACH a file. What I have tried:
Using subprocess, as seen/asked here and here:
import subprocess
p = subprocess.Popen(["gnome-terminal","--disable-factory","--","./daophot"], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
p.stdin.write(input("ATTACH file.fits"))
For this case, DAOPHOT's shell opens but the ATTACH command is not executed. I close the shell and the string "ATTACH file.fits" appears in the IPython terminal, ending the subprocess. I've tried also to use p.communicate(input=input("ATTACH file.fits")), but got the same result.
Using pexpect, as seen/asked here and here:
import pexpect
p = pexpect.spawn("gnome-terminal --disable factory -- ./daophot")
p.expect(pexpect.EOF)
p.sendline("ATTACH file.fits")
In this case, DAOPHOT's shell opens but the ATTACH command is not accounted for as an input.
Finally, a DAOPHOT wrapper already exists, but the idea is to have this automatically and interactive Python version in our lab, so that we can change later if needed.
From what I understand in terms of pipelines, ./daophot is a subsubprocess runnning inside gnome-terminal, so when I use e.g. p.stdin.write(input("ATTACH file.fits") I am actually inputing this command into gnome-terminal, and not into ./daophot.
Any help is much appreciated.

How to write a python script to interact with shell scripts

I encountered a problem in work. Here it is.
I have several scripts(mostly are shell scripts) to execute, and I want to write a python script to run them automatically. One of these shell scripts needs interactive input during it's execution. What troubled me is that I can't find a way to read its input prompt, so I can't decide what to enter to continue.
I simplified the problem to something like this:
There is a script named mediator.py, which run greeter.sh inside. The mediator takes greeter's input prompt and print it to the user, then gets user's input and pass it to greeter. The mediator needs to act exactly the same as the greeter from user's point of view.
Here is greeter.sh:
#! /bin/bash
echo "Please enter your name: " # <- I want 'mediator.py' to read this prompt and show it to me, and then get what I input, then pass my input to 'greeter.sh'
read name
echo "Hello, " $name
I want to do this in the following order:
The user (that's me) run mediator.py
The mediator run greeter.sh inside
The mediator get the input prompt of greeter, and output it on the screen.(At this time, the greeter is waiting for user's input. This is the main problem I stuck with)
The user input a string (for example, 'Mike'), mediator get the string 'Mike' and transmit it to greeter
The greeter get the name 'Mike', and print a greeting
The mediator get the greeting, and output it on the screen.
I searched for some solution and determined to use Popen function in subprocess module with stdout of sub-process directed to PIPE, it's something like this:
sb = subprocess.Popen(['sh', 'greeter.sh'], stdout = subprocess.PIPE, stdin = stdout, stderr = stdout)
but I can't solve the main problem in step 3 above. Can anyone give me some advice for help? Thanks very much!
You make it much more complicated (and brittle) than it has to be. Instead of coding everything at the top-level and try to use subprocess or whatever to use your scripts as if they where functions, just write modules and functions and use them from your main script.
Here's an example with all contained in the script itself, but you can split it into distinct modules if you need to share some functions between different scripts
# main.py
def ask_name():
return raw_input("Please enter your name: ")
def greet(name):
return "Hello, {} name !\n".format(name)
def main():
name = ask_name()
print greet(name)
if __name__ == "__main__":
main()

How can I effectively test my readline-based Python program using subprocesses?

I have a Python program, which, under certain conditions, should prompt the user for a filename. However, there is a default filename which I want to provide, which the user can edit if they wish. This means typically that they need to hit the backspace key to delete the current filename and replace it with the one they prefer.
To do this, I've adapted this answer for Python 3, into:
def rlinput(prompt, prefill=''):
readline.set_startup_hook(lambda: readline.insert_text(prefill))
try:
return input(prompt)
finally:
readline.set_startup_hook()
new_filename = rlinput("What filename do you want?", "foo.txt")
This works as expected when the program is run interactively as intended - after backspacing and entering a new filename, new_filename contains bar.txt or whatever filename the user enters.
However, I also want to test the program using unit tests. Generally, to do this, I run the program as a subprocess, so that I can feed it input to stdin (and hence test it as a user would use it). I have some unit testing code which (simplified) looks like this:
p = Popen(['mypythonutility', 'some', 'arguments'], stdin=PIPE)
p.communicate('\b\b\bbar.txt')
My intention is that this should simulate the user 'backspacing' over the provided foo.txt, and entering bar.txt instead.
However, this doesn't seem to have the desired effect. Instead, it would appear, after some debugging, that new_filename in my program ends up with the equivalent of \b\b\bbar.txt in it. I was expecting just bar.txt.
What am I doing wrong?
The appropriate way to control an interactive child process from Python is to use the pexpect module. This module makes the child process believe that it is running in an interactive terminal session, and lets the parent process determine exactly which keystrokes are sent to the child process.
Pexpect is a pure Python module for spawning child applications; controlling them; and responding to expected patterns in their output. Pexpect works like Don Libes’ Expect. Pexpect allows your script to spawn a child application and control it as if a human were typing commands.

Is it possible to call a program and also obtain the input of that program in Python?

I can call another program in Python using
subprocess.call(["do_something.bat"])
I want to know if I can collect the stdin input of do_something.bat?
do_something.bat is a launcher for a Java program, the Java program will prompt the user to enter project specific information such as project name, version, and will generate a project skeleton according to the user input.
I use python to call this do_something.bat, and after it generates all the projects files, I need continue to go to a specific directory under project root, but that requires to know the project name, can I get the project name that the user previously entered?
It depends a bit on how do_something.bat prompts the user.
If it simply reads from standard input your program can act as a go-between. It can prompt the output of do_something.bat, read the user's response, and then pipe the response back to the standard input of do_something.bat.
Otherwise, I do not think it is possible without adapting do_something.bat.
If you know what the exact parameters that the program will ask for and what order it will ask for them then you can collect the arguments yourself and forward them on to the subprocess.
eg.
from subprocess import Popen, PIPE
# get inputs
input1 = ...
input2 = ...
child = Popen("do_something.bat", stdin=PIPE)
# Send data to stdin. Would also return data from stdout and stderr if we set
# those arguments to PIPE as well -- they are returned as a tuple
child.communicate("\n".join([input1, input2, ...]))
if child.returncode != 0:
print "do_something.bat failed to execute successfully"

Retrieving Raw_Input from a system ran script

I'm using the OS.System command to call a python script.
example:
OS.System("call jython script.py")
In the script I'm calling, the following command is present:
x = raw_input("Waiting for input")
If I run script.py from the command line I can input data no problem, if I run it via the automated approach I get an EOFError. I've read in the past that this happens because the system expects a computer to be running it and therefore could never receive input data in this way.
So the question is how can I get python to wait for user input while being run in an automated way?
The problem is the way you run your child script. Since you use os.system() the script's input channel is closed immediately and the raw_input() prompt hits an EOF (end of file). And even if that didn't happen, you wouldn't have a way to actually send some input text to the child as I assume you'd want given that you are using raw_input().
You should use the subprocess module instead.
import subprocess
from subprocess import PIPE
p = subprocess.Popen(["jython", "script.py"], stdin=PIPE, stdout=PIPE)
print p.communicate("My input")
Your question is a bit unclear. What is the process calling your Python script and how is it being run? If the parent process has no standard input, the child won't have it either.

Categories