I'm trying to understand some basic shell scripting. I have a script.sh, did the chmod, and was messing around with some pretty easy print statements by executing ./script.sh
Now how could I launch the shell displaying a prompt that includes the current working directory, and said prompt should accept a line of input and display a prompt each time?
To sum up the tools I understand so far: os.getcwd(), sys.stdin.readlines(), subprocess.Popen(['ls'], stdout=subproccess.PIPE)
Here is what I have so far.
#!/usr/bin/env python
import os
import sys
import subprocess
proc = subprocess.Popen(['ls'], stdout=subprocess.PIPE)
cwd = os.getcwd()
while True:
user_input = raw_input(str(cwd) + " >> ")
if user_input == 'ls':
print proc
if not foo:
sys.exit()
So this seems to work. At least the command prompt part, not exiting.
If you want to prompt the user, then you probably don't want to be using sys.stdin.readlines() as there isn't really an easy way to put your prompt in after each line. Instead, use input() (or raw_input() on Python 2).
user_input = input("My prompt text> ")
Then the user's input will be stored in a string in user_input. Put that in a while loop, and you can have it repeatedly display, like a regular command prompt.
Related
I am trying to write a Python script that automatically grades a Python script submitted by a student, where the student's script uses the input() function to get some information from the user.
Suppose the student's script is something simple like this:
name = input('Enter your name: ')
print(f'Hello {name}!')
The portion of the test script that runs the student script is something like this:
import subprocess
run_cmd = 'python student_script.py'
test_input = 'Bob'
p = subprocess.run(run_cmd.split(), input=test_input, capture_output=True, text=True)
After running that portion of the test script, output from the student's script is captured and can be accessed via p.stdout which is a string having this value:
'Enter your name: Hello Bob!\n'
No surprise there, since this is everything output by the student script, but notice that the 'Bob' test input is not included.
In the test report, I want to show the script input and output in the same way that it would appear if the script had been run from a command line, which would look like this:
Enter your name: Bob
Hello Bob!
Given that the scripts are written by students, the prompt message output by the student script could be anything (e.g., What is your name?, Who are you?, Type in name:, etc.) and the student script might also print something other than 'Hello Bob!', so I don't think there is any way to reliably figure out where to correctly insert the 'Bob' test input (and a trailing new line) into p.stdout.
Is there a way to get subprocess.run() to capture interlaced stdin and stdout?
Or is there another way to run a Python script from a Python script that captures interlaced stdin and stdout?
Ideally, for this example, I would be able to get a string having this value:
'Enter your name: Bob\nHello Bob!\n'
I've search SO and read through the subprocess documentation, but thus far I've come up short on finding a solution.
Here's the solution I came up with. I expect there is a more elegant way to do it, but it works on the Ubuntu Linux computer that the automated test scripts run on. I have not tried it on Windows, but I believe it will not work since os.set_blocking() is only supported on Unix per the os module documentation.
import subprocess
import os
import time
run_cmd = 'python student_script.py'
test_input = 'Bob'
# Start the student script running
p = subprocess.Popen(run_cmd.split(), stdin=subprocess.PIPE, stdout=subprocess.PIPE, text = True)
# Give the script some time to run
time.sleep(2)
# String to hold interleaved stdin and stdout text
stdio_text = ''
# Capture everything from stdout
os.set_blocking(p.stdout.fileno(), False) # Prevents readline() blocking
stdout_text = p.stdout.readline()
while stdout_text != '':
stdio_text += stdout_text
stdout_text = p.stdout.readline()
# Append test input to interleaved stdin and stdout text
stdio_text += (test_input + '\n')
try:
# Send test input to stdin and wait for student script to terminate
stdio_text += p.communicate(input=test_input, timeout=5)[0]
except subprocess.TimeoutExpired:
# Something is wrong with student script
pass
p.terminate()
The key to this solution working is os.set_blocking(), which I found out about here. Without it readline() blocks indefinitely.
I don't love the time.sleep(2) since it assumes it will take 2 seconds or less for the student script to reach the point where it calls input(), but there does not seem to be any way to determine when a process is looking for input from stdin. The sleep time could be increased for longer scripts.
If you've got any ideas for improvements, please share.
I'm working on a script to automate tests of a certain software, and as part of it I need to chech if it runs commands correctly.
I'm currently launching an executeable using subprocess and passing the initial parameters.
My code is: subprocess.run("program.exe get -n WiiVNC", shell=True, check=True)
As far as I understand, this runs the executeable, and is supposed to return an exception if the exit code is 1.
Now, the program launches, but at some point waits for user input like so:
My question is, how do I go about submitting the user input "y" using subprocess once either, the text "Continue with download of "WiiVNC"? (y/n) >" shows up, or once the program waits for user input.
You should use the pexpect module for all complicated subprocessing. In particular, the module is designed to handle the complicated case of either passing through the input to the current process for the user to answer and/or allowing your script to answer the input for the user and continue the subprocess.
Added some code for an example:
### File Temp ###
# #!/bin/env python
# x = input('Type something:')
# print(x)
import pexpect
x = pexpect.spawn('python temp') #Start subprocess.
x.interact() #Imbed subprocess in current process.
# or
x = pexpect.spawn('python temp') #Start subprocess.
find_this_output = x.expect(['Type something:'])
if find_this_output is 0:
x.send('I type this in for subprocess because I found the 0th string.')
Try this:
import subprocess
process = subprocess.Popen("program.exe get -n WiiVNC", stdin=subprocess.PIPE, shell=True)
process.stdin.write(b"y\n")
process.stdin.flush()
stdout, stderr = process.communicate()
I've managed to get the cmd being opened by python. However, using runas administrator comes with a password check before cmd.exe is executed.
I'm using this to open cmd...
import subprocess
subprocess.call(["runas", "/user:Administrator", "cmd.exe"])
I'm looking for a way to automatically enter the password into the runas.exe prompt which opens when i run the code. Say if i were to create var = "test" and add it after import subprocess how would i make it so that this variable is passed to and seen as an input to the runas.exe?
The solution would require only python modules which are in version 3.4 or higher.
Update
I have found some code which appears to input straight into runas.exe. However, the apparent input is \x00\r\n when in the code the input is supposed to be test I am fairly certain that if i can get the input to be test then the code will be successful.
The code is as follows :
import subprocess
args = ['runas', '/user:Administrator', 'cmd.exe']
proc = subprocess.Popen(args,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
proc.stdin.write(b'test\n')
proc.stdin.flush()
stdout, stderr = proc.communicate()
print (stdout)
print (stderr)
Although not an answer to your question, this can be a solution to your problem. Use psexec instead of runas. You can run it like this:
psexec -u user -p password cmd
(or run it from Python using subprocess.Popen or something else)
This piece of code actually works (tested on a Windows 2008 server). I've used it to call runas for a different user and pass his password. A new command prompt opened with new user context, without needing to enter password.
Note that you have to install pywin32 to have access to the win32 API.
The idea is:
to Popen the runas command, without any input redirection, redirecting output
read char by char until we encounter ":" (last char of the password prompt).
send key events to the console using win32 packages, with the final \r to end the password input.
(adapted from this code):
import win32console, win32con, time
import subprocess
username = "me"
domain = "my_domain"
password ="xxx"
free_console=True
try:
win32console.AllocConsole()
except win32console.error as exc:
if exc.winerror!=5:
raise
## only free console if one was created successfully
free_console=False
stdin=win32console.GetStdHandle(win32console.STD_INPUT_HANDLE)
p = subprocess.Popen(["runas",r"/user:{}\{}".format(domain,username),"cmd.exe"],stdout=subprocess.PIPE)
while True:
if p.stdout.read(1)==b":":
for c in "{}\r".format(password): # end by CR to send "RETURN"
## write some records to the input queue
x=win32console.PyINPUT_RECORDType(win32console.KEY_EVENT)
x.Char=unicode(c) # remove unicode for python 3
x.KeyDown=True
x.RepeatCount=1
x.VirtualKeyCode=0x0
x.ControlKeyState=win32con.SHIFT_PRESSED
stdin.WriteConsoleInput([x])
p.wait()
break
I am trying to open a executable that opens a HEC .dss database file. However, I can only seem to get it to read one argument after opening the exe and then it doesn't read anything else. Is there any way to force it to keep inserting commands.
This exe has some unique features to it, which include that the first command asks what DSS file you are going to read. Then you can input a command to create the output txt file that it will write to for the rest of the commands. What I've been able to do so far is to start the program and run one command into the exe (the mydss variable). However, after that first command is read, none of the other commands are used in the command prompt. I feel like I'm missing something here. Here is the code:
##Testing on how to run and use the DSSUTL program
import subprocess
from subprocess import PIPE, STDOUT
DSSUTL = "C:\Users\sduncan\Documents\HEC-DSS\HEC-DSSVue-2_0_1\FromSivaSel\DSSUTL.exe"
mydss = "C:\Users\sduncan\Documents\HEC-DSS\HEC-DSSVue-2_0_1\FromSivaSel\\forecast.dss"
firstLine = "WR.T TO=PythonTextOutput.txt"
commandLine = "WR.T B=SHAVER RESERVOIR-POOL C=FLOW-IN E=1HOUR F=10203040"
myList = [firstLine, commandLine]
ps = subprocess.Popen([DSSUTL, mydss, myList[1], myList[0]], shell=True)
I've also tried including stdin=subprocess.PIPE, but that only leads to the exe opening and it is blank (when I open it with the code above I can read it and see that the mydss variable was read correctly). When I used stdout or sterr, the program only opens and closes.
I've also tried using the code when the stdin=PIPE was turned on with:
ps.stdin.write(myList[1])
ps.stdin.write(myList[0])
ps.communicate()[0]
However, it did not read anything in the program. This program runs like a command prompt, however, it's not the typical cmd as it was made to read the DSS filetype and produce a text file with the list from searches like in the commandLine variable
It would be nice to know what I could do to fix the code so that I could input the extra commands. Any help to know how to event check if the commands were being sent or processed by this exe. Eventually, I will be adding many more commands to the exe file to print to the text file, so if there is any way to get python to write to the exe that would help.
#tdelaney, #eryksun Thank you for commenting, your comments about the pipes and delay really helped. I was able to fix the problem by using this code:
##Testing on how to run and use the DSSUTL program
import subprocess
from subprocess import PIPE, STDOUT
import time
DSSUTL = "C:\Users\sduncan\Documents\HEC-DSS\HEC-DSSVue-2_0_1\FromSivaSel\DSSUTL.exe"
mydss = "C:\Users\sduncan\Documents\HEC-DSS\HEC-DSSVue-2_0_1\FromSivaSel\\forecast.dss"
location = "WR.T TO=PythonTextOutput.txt" + " WR.T B=SHAVER RESERVOIR-POOL C=FLOW-IN E=1HOUR F=10203040" + "\n"
filecontent1 = "WR.T B=FLORENCE RESERVOIR-POOL C=FLOW-IN E=1HOUR F=10203040" + "\n"
filecontent2 = "WR.T B=HUNTINGTON LAKE-POOL C=FLOW-IN E=1HOUR F=10203040" + "\n"
filecontentList = [filecontent1, filecontent2]
myList = [DSSUTL, mydss] # commandLine, location
ps = subprocess.Popen(myList , shell=False, stdin=PIPE, stdout=PIPE, stderr=STDOUT)
time.sleep(1)
# input into stdin
ps.stdin.write(location)
time.sleep(1)
ps.stdin.write(filecontent1)
time.sleep(1)
ps.stdin.write(filecontent2)
time.sleep(1)
print ps.communicate()[0]
# End Script
By using the pipes to talk to the program and putting a time delay seemed to fix the problem and allowed me to talk to the console. Even though the console display is blank, by printing the communicate() command, it outputs what the console did and produce the text file with the wanted series.
Thanks for pushing me in the right direction!
I am executing a script which prompts for 2 values one after the other. I want to pass the values from the script itself as I want to automate this.
Using the subprocess module, I can easily pass one value:
suppression_output = subprocess.Popen(cmd_suppression, shell=True,
stdin= subprocess.PIPE,
stdout= subprocess.PIPE).communicate('y') [0]
But passing the 2nd value does not seem to work. If I do something like this:
suppression_output = subprocess.Popen(cmd_suppression, shell=True,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE).communicate('y/r/npassword')[0]
You should use \n for the new line instead of /r/n -> 'y\npassword'
As your question is not clear, I assumed you have a program which behaves somewhat like this python script, lets call it script1.py:
import getpass
import sys
firstanswer=raw_input("Do you wish to continue?")
if firstanswer!="y":
sys.exit(0) #leave program
secondanswer=raw_input("Enter your secret password:\n")
#secondanswer=getpass.getpass("Enter your secret password:\n")
print "Password was entered successfully"
#do useful stuff here...
print "I should not print it out, but what the heck: "+secondanswer
It asks for confirmation ("y"), then wants you to enter a password. After that it does "something useful", finally prints the password and then exits
Now to get the first program to be run by a second script script2.py it has to look somewhat like this:
import subprocess
cmd_suppression="python ./testscript.py"
process=subprocess.Popen(cmd_suppression,shell=True\
,stdin=subprocess.PIPE,stdout=subprocess.PIPE)
response=process.communicate("y\npassword")
print response[0]
The output of script2.py:
$ python ./script2.py
Do you wish to continue?Enter your secret password:
Password was entered successfully
I should not print it out, but what the heck: password
A problem can most likely appear if the program uses a special method to get the password in a secure way, i.e. if it uses the line I just commented out in script1.py
secondanswer=getpass.getpass("Enter your secret password:\n")
This case tells you that it is probably not a good idea anyway to pass a password via a script.
Also keep in mind that calling subprocess.Popen with the shell=True option is generally a bad idea too. Use shell=False and provide the command as a list of arguments instead:
cmd_suppression=["python","./testscript2.py"]
process=subprocess.Popen(cmd_suppression,shell=False,\
stdin=subprocess.PIPE,stdout=subprocess.PIPE)
It is mentioned a dozen times in the Subprocess Documentation
Try os.linesep:
import os
from subprocess import Popen, PIPE
p = Popen(args, stdin=PIPE, stdout=PIPE)
output = p.communicate(os.linesep.join(['the first input', 'the 2nd']))[0]
rc = p.returncode
In Python 3.4+, you could use check_output():
import os
from subprocess import check_output
input_values = os.linesep.join(['the first input', 'the 2nd']).encode()
output = check_output(args, input=input_values)
Note: the child script might ask for a password directly from the terminal without using subprocess' stdin/stdout. In that case, you might need pexpect, or pty modules. See Q: Why not just use a pipe (popen())?
import os
from pexpect import run # $ pip install pexpect
nl = os.linesep
output, rc = run(command, events={'nodes.*:': 'y'+nl, 'password:': 'test123'+nl},
withexitstatus=1)