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()
Related
This is a weird one that's so general I can't to properly narrow the search terms to find an answer.
My python script has a raw_input to prompt the user for values. But, when I try to run the script and funnel it into a file, it crashes.
Something like "script.py > save.txt"
wont work. It doesn't even properly prompt me at the command line for my input. There doesn't seem to be anything indicating why this doesn't work as intuitively as it should.
raw_output prints its prompt to stdout, which you are redirecting to a file. So your prompt will end up in the file and the program does not appear to show a prompt. One solution is to output your prompt to stderr.
import sys
sys.stderr.write('prompt> ')
value = raw_input()
print('value was: ', value)
You could also avoid using both pipes and interactive input with the same script. Either take input from command line flags using argparse and use pipes, or create an interactive program that saves output to a file itself.
Depending on your program's logic, you can also check whether stdout is connected to a live console or not:
is_tty = os.isatty(sys.stdout.fileno())
Dolda2000 also has a good point about writing to /dev/tty, which will write to the controlling terminal of the script being run even if both stdin and stderr are redirected. The deal there, though, is that you can't use it if you're not running in a terminal.
import errno
try:
with open('/dev/tty', 'w') as tty:
tty.write('prompt> ')
except IOError as exc:
if exc.errno == errno.ENXIO:
pass # no /dev/tty available
else:
pass # something else went wrong
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.
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"
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().
I'm having troubles getting this to work. Basically I have a python program that expect some data in stdin, that is reading it as sys.stdin.readlines() I have tested this and it is working without problems with things like echo "" | myprogram.py
I have a second program that using the subprocess module calls on the first program with the following code
proc = subprocess.Popen(final_shell_cmd,
stderr=subprocess.PIPE, stdout=subprocess.PIPE,
shell=False), env=shell_env)
f = ' '.join(shell_cmd_args)
#f.append('\4')
return proc.communicate(f)
The second program is a daemon and i have discovered that the second program works well as long as I hit ctrl-d after calling it from the first program.
So it seems there is something wrong with subprocess not closing the file and my first program expecting more input when nothing more should be sending.
anyone has any idea how I can get this working?
The main problem here is that "shell_cmd_args" may contain passwords and other sensitive information that we do not want to pass in as the command name as it will show in tools like "ps".
You want to redirect the subprocess's stdin, so you need stdin=subprocess.PIPE.
You should not need to write Control-D ('\4') to the file object. Control-D tells the shell to close the standard input that's connected to the program. The program doesn't see a Control-D character in that context.