Using python to interact with a command line program - python

(new to python so bear with me...)
I have a simple java jar file that when executed, it asks the user for some basic data in a command prompt and then executes an operation based on this data.
I want to write a simple python script that will automate the execution of this jar file without the need for the user to supply the data, meaning writing the data inside the script and basically interact with the java application when asked by the command prompt.
Think of a java file that is executed by running Java -jar someJavaApp.jar
Then a command prompt is asking for you name. you enter it and press enter, then it asks for your last name, you enter it and press enter and then the java app executes an arbitrary operation based on the input.
I want to have a python script that I can execute the java app several times, each time with different inputs taken from a file or something similar.
Any help will be appreciated.
Thanks.

You can use Popen from the subprocess module.
Here's a simple example, first script to prompt for input and return some output, test_input.py:
first = input("Enter your first name: ")
second = input("Enter your second name: ")
print(first+second)
Then the script to call the first script:
import subprocess
values = (("A", "B"), ("C", "D"), ("E", "F"))
command = "python test_input.py"
for first, second in values:
# lazy use of universal_newlines to prevent the need for encoding/decoding
p = subprocess.Popen(command, stdin=subprocess.PIPE, stdout=subprocess.PIPE, shell=True, universal_newlines=True)
output, err = p.communicate(input="{}\n{}\n".format(first, second))
# stderr is not connected to a pipe, so err is None
print(first, second, "->", end="")
# we just want the result of the command
print(output[output.rfind(" "):-1]) # -1 to strip the final newline
This will print:
A B -> AB
C D -> CD
E F -> EF
If you have problems getting it working with your java program, a quick solution may be to specify shell=True in the Popen function call. But you'll want to be aware of the security considerations.

Related

How to communicate in Runtime between 2 computers (PC1, PC2) through PowerShell (via SSH) using subprocess.Popen?

Description of the situation:
PC1 is a Windows 10 computer. PC2 is a computer with an Ubuntu operating system, on a very old version (12.04.5) which has a limited connection to the Internet (nothing can be installed on it). Also on it is a test system.
The goal is to create a script that controls in runtime all the inputs and outputs from PC2 during the running of the tests, because sometimes it is necessary for the user to intervene and enter test configuration data after which it can continue working.Output processing and writing to stdin must be automated.
First attempt:
In the first attempt, I wrote a main script, let's call it RuntimeListner.py, which starts several scripts with user input, which will have to be started one by one and all outputs are processed in runtime and when a user is needed input, then this script writes it with Popen.stdin.write(bytes("command".encode())).
from pathlib import Path
import subprocess
DATA_BANK_SCRIPTS = {
"HelloUser": {
"Please write your name:\r": "Stack",
"Please write your age:\r": "99"
},
"AddTwoNumbers": {
"Please write first number:\r": "10",
"Please write second number:\r": "20",
}
}
process = subprocess.Popen(["PowerShell.exe"], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
for key, value_dict in DATA_BANK_SCRIPTS.items():
script_path = Path(__file__).resolve().parent.joinpath(f"Tests\{key}.py")
process.stdin.write(bytes(f"python {script_path}\n".encode()))
process.stdin.flush()
current_output = process.stdout.readline()
while current_output:
current_line = current_output.decode()
current_line = current_line.rstrip("\n")
print(current_line)
if current_line in value_dict:
process.stdin.write(bytes((value_dict[current_line] + '\n').encode()))
process.stdin.flush()
current_output = process.stdout.readline()
else:
current_output = process.stdout.readline()
This demo script starts other scripts and waits until there is user input, after which it processes it and enters the data from the set dictionary.
This is how the script version works. This works very well.
Second attempt:
In the second attempt, I already want to have a separate PowerShell window, to have total control over it, so that I can take over all input/output from it.
powershell_path = r"C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe"
process = subprocess.Popen(["start", powershell_path, "-ExecutionPolicy","Bypass","-NoExit"],cwd=r"C:\Windows\System32\\", shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
This code block opens a new PowerSehll window, but further I can no longer call a script directly in this window and process input/output.
If I want to start any command in powershell then nothing is written.
For example:
process.stdin.write(bytes(f"python.exe {script_path}\n".encode()))
process.stdin.flush()
In the same way, nothing is written in the console and nothing is printed.
I also tried nested subprocess.Popen, by assigning stdin from the first process to the second process, but also no reaction. And also stdout.readline() freezes.
Is there another method to solve this problem? Maybe I'm wrong somewhere.

How to capture interlaced stdin and stdout from a Python subprocess

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.

How to run cmd.exe in python

I'm trying to port the following c# code into Python. It firstly defines a new process and then runs a windows prompt command (cmd.exe). After that, it executes a command in the prompt and when an external event occurs, it closes the prompt.
//Start the prompt - when an event occured
Process winShell = new Process();
winShell.StartInfo.FileName = "cmd.exe";
winShell.StartInfo.RedirectStandardInput = true;
winShell.Start();
//Execute a command in the prompt
winShell.StandardInput.WriteLine("cd " + projectDirectory);
//Close it - when an event occured
winShell.StandardInput.Flush();
winShell.StandardInput.Close();
winShell.WaitForExit();
I read that for Python 3 (my version 3.7), It is recommended to use subprocess. Unfortunately, I feel a bit confused about which of the function to use. I found call, run and Popen, but I didn't understand how to use them.
I wrote the following lines, but they don't produce any visible result.
import subprocess
subprocess.run(['cmd.exe'])
First of all, I would like that the shell appears and than to write some commands in it. Finally, I want to close it.
Use subprocess.Popen() like this. Each API matches to the corresponding C# API almost 1:1.
p = subprocess.Popen(['cmd.exe'],
stdin=subprocess.PIPE, stdout=subprocess.PIPE,
text=True)
p.stdin.write('dir\n')
p.stdin.close()
print(p.stdout.read())
p.wait()
p.stdout.close()
Other API's such as run(), call(), etc are wrappers for Popen(). For example, the above code is equivalent to this one line.
print(subprocess.run(['cmd.exe'], capture_output=True, text=True, input = 'dir\n').stdout)

Python: run shell script with manual input

For example, the shell script takes an integer at a prompt and returns it.
Enter an integer:
--> 3
3
I'm using subprocess.check_call(["./myScript"]) to run the shell script. How can I automate sending the "3" in as in the example above? So far all my searching has only recovered how to run a script with command line arguments, not this kind of manual input.
As the earlier answer explained subprocess.Popen can be used to create process that can be interacted with communicate. communicate takes string as a parameter that will be passed to the created process and returns tuple (stdout, stderr). Below is a short example of two Python scripts communicating with it:
Child
nums = raw_input()
print sum((int(n) for n in nums.split()))
Parent
import subprocess
p = subprocess.Popen(['python', 'test.py'], stdin=subprocess.PIPE, stdout=subprocess.PIPE)
out, err = p.communicate('3 4 5')
print 'From other process: ' + out
Output
From other process: 12
You probably want to use the subprocess.Popen.communicate() function. The docs are quite expressive.

python subprocess.Popen stdin.write

I'm new to python and would like to open a windows cmd prompt, start a process, leave the process running and then issue commands to the same running process.
The commands will change so i cant just include these commands in the cmdline variable below. Also, the process takes 10-15 seconds to start so i dont want to waste time waiting for the process to start and run commands each time. just want to start process once. and run quick commands as needed in the same process
I was hoping to use subprocess.Popen to make this work, though i am open to better methods. Note that my process to run is not cmd, but im just using this as example
import subprocess
cmdline = ['cmd', '/k']
cmd = subprocess.Popen(cmdline, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
cmd.stdin.write("echo hi") #would like this to be written to the cmd prompt
print cmd.stdout.readline() #would like to see 'hi' readback
cmd.stdin.write("echo hi again") #would like this to be written to the cmd prompt
print cmd.stdout.readline() #would like to see 'hi again' readback
The results arent what i expect. Seems as though the stdin.write commands arent actually getting in and the readline freezes up with nothing to read.
I have tried the popen.communicate() instead of write/readline, but it kills the process. I have tried setting bufsize in the Popen line, but that didn't make too much difference
Your comments suggest that you are confusing command-line arguments with input via stdin. Namely, the fact that system-console.exe program accepts script=filename parameter does not imply that you can send it the same string as a command via stdin e.g., python executable accepts -c "print(1)" command-line arguments but it is a SyntaxError if you pass it as a command to Python shell.
Therefore, the first step is to use the correct syntax. Suppose the system-console.exe accepts a filename by itself:
#!/usr/bin/env python3
import time
from subprocess import Popen, PIPE
with Popen(r'C:\full\path\to\system-console.exe -cli -',
stdin=PIPE, bufsize=1, universal_newlines=True) as shell:
for _ in range(10):
print('capture.tcl', file=shell.stdin, flush=True)
time.sleep(5)
Note: if you've redirected more than one stream e.g., stdin, stdout then you should read/write both streams concurrently (e.g., using multiple threads) otherwise it is very easy to deadlock your program.
Related:
Q: Why not just use a pipe (popen())? -- mandatory reading for Unix environment but it might also be applicable for some programs on Windows
subprocess readline hangs waiting for EOF -- code example on how to pass multiple inputs, read multiple outputs using subprocess, pexpect modules.
The second and the following steps might have to deal with buffering issues on the side of the child process (out of your hands on Windows), whether system-console allows to redirect its stdin/stdout or whether it works with a console directly, and character encoding issues (how various commands in the pipeline encode text).
Here is some code that I tested and is working on Windows 10, Quartus Prime 15.1 and Python 3.5
import subprocess
class altera_system_console:
def __init__(self):
sc_path = r'C:\altera_lite\15.1\quartus\sopc_builder\bin\system-console.exe --cli --disable_readline'
self.console = subprocess.Popen(sc_path, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
def read_output(self):
rtn = ""
loop = True
i = 0
match = '% '
while loop:
out = self.console.stdout.read1(1)
if bytes(match[i],'utf-8') == out:
i = i+1
if i==len(match):
loop=False
else:
rtn = rtn + out.decode('utf-8')
return rtn
def cmd(self,cmd_string):
self.console.stdin.write(bytes(cmd_string+'\n','utf-8'))
self.console.stdin.flush()
c = altera_system_console()
print(c.read_output())
c.cmd('set jtag_master [lindex [get_service_paths master] 0]')
print(c.read_output())
c.cmd('open_service master $jtag_master')
print(c.read_output())
c.cmd('master_write_8 $jtag_master 0x00 0xFF')
print(c.read_output())
You need to use iter if you want to see the output in real time:
import subprocess
cmdline = ['cmd', '/k']
cmd = subprocess.Popen(cmdline, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
cmd.stdin.write("echo hi\n")#would like this to be written to the cmd prompt
for line in iter(cmd.stdout.readline,""):
print line
cmd.stdin.write("echo hi again\n")#would like this to be written to the cmd prompt
Not sure exactly what you are trying to do but if you want to input certain data when you get certain output then I would recommend using pexpect

Categories

Resources