Running a batch file with subprocess - python

I'd like to execute a simple batch file using Python. But I'm getting some error back from the process saying the file, directory or disc name is not right. I guess the best way to start is to show the code:
import subprocess as sp
from pathlib import Path
file = Path(r'C:\Program Files (x86)\test.bat')
p = sp.Popen(['"' + str(file) + '"'], stdin=sp.PIPE, stdout=sp.PIPE, stderr=sp.PIPE,
shell=True, universal_newlines=True)
outs, errs = p.communicate('', timeout=5)
print(outs, '\n-----\n', errs)
I extended this with appending to system path and changing the working directory:
import os
import sys
sys.path.append(file.parent)
os.chdir(file.parent)
The batch file contains just a few echo commands for debugging. So I'd expect the code above to print the contents of the echoes. I've verified that I'm able to call it inside a command prompt from any folder. Previously I was getting some file permission error (WinError 5), so that might be related especially as the file is in Program Files. This error was not from the process, but Python itself.
I also tried it with an executable, and a similar error was raised: WinError 2: the system cannot find the file specified. Any idea where I'm stumbling?
Attempts
When the shell=True keyword is removed, the WinError 5 is back
When Popen is called with ['cmd'] and the batch file is run with p.communicate('"' + str(file) + '"\n', timeout=5), no errors are thrown, and the output contains the echoes. However batch files should run without explicitly opening a command prompt, I presume.

Use a command-line string instead of an args list when passing shell=True or when running a batch script with the default shell=False.
On Windows, Popen processes an args list into a command line that's compatible with VC++ argument parsing. But cmd.exe don't use VC++ argument parsing rules, and even for an executable that does use VC++ rules (e.g. python.exe), the extra quotes you're adding (i.e. '"' + str(file) + '"') get backslash-escaped as literal quotation marks in the command line.

Related

Subprocess command throwing error while converting docx to pdf

I'm running the following code on my windows machine using python's subprocess module.
import subprocess
args = ["abiword --to=pdf '{}'".format('test.docx')]
process = subprocess.run(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True, timeout=None)
print(process.stdout.decode())
filename = re.search('-> (.*?) using filter', process.stdout.decode())
print(filename.group(1))
But subprocess.run gives the following error:
b'\'"abiword --to=pdf \'test.docx\'"\' is not recognized as an internal or external command,\r\noperable program or batch file.\r\n'
How to resolve this error and how should I proceed now?
Also, is it correct to use abiword command in my windows machine? I want to convert my docx to pdf without installing any third party software like libreoffice.
You have 2 different problems here.
First one will be simple to solve: you do not give the correct parameters to subprocess. The first parameter (args) can be either a string which contains the full command line or a list containing the command and parameters as separate elements. So you should use either:
args = "abiword --to=pdf '{}'".format('test.docx')
(a simple string and not a list) or:
args = ["abiword", "--to=pdf", '{}'.format('test.docx')]
The second one is that until you can generate the pdf by entering the abiword --to-pdf command in a console CMD.exe window, the same command launched with subprocess.run will not give better results...
Use the full path to abiword: /usr/bin/abiword

why does subprocess fails during the given task?

I am using subprocess instead of os.system to execute a shell command to convert a document to pdf asynchronously. When being called repeatedly, it fails for approx 50% of files. It doesn't convert the doc file to pdf. How can I fix it.
conversion_process = await asyncio.create_subprocess_shell( 'soffice -env:UserInstallation=file:///tmp/LibreOffice_Conversion_' + author + ' --headless --convert-to pdf --outdir ' + folder + ' ' + input_file, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
stdout, stderr = await conversion_process.communicate()
if stderr:
app.logger.error(f"stderr: {stderr}")
error message:
stderr: b'javaldx: Could not find a Java Runtime Environment!
Please ensure that a JVM and the package libreoffice-java-common
is installed.
If it is already installed then try removing ~/.config/libreoffice/4/user/config/javasettings_Linux_*.xml
Warning: failed to read path from javaldx
The reason you get the error message is because the path variable used by the subprocess and passed to LibreOffice is not sufficient to find the JRE. I ran into this same issue and changed it to the following which seems to fix it.
subprocess.run(cmd,env={'HOME':'/home/username'})
I can likewise confirm that this error does not happen every time it is called, my speculation as to why this occurs is that JRE is only needed for some document conversions, not all.

Python double quotes in subprocess.Popen aren't working when executing WinSCP scripting

I am attempting to use the code from here https://stackoverflow.com/a/56454579 to upload files to a server with WinSCP from Python on Windows 10. The code looks like this:
import subprocess
path = r'C:\mp4s\Sci-Fi Light Flicker.mp4'
process = subprocess.Popen(
['C:\Program Files (x86)\WinSCP\WinSCP.com', '/ini=nul', '/command', 'option batch abort', 'option confirm off', 'open ftp://user:pass#ftp.website.com', f'put "{path}"', 'exit'],
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
for line in iter(process.stdout.readline, b''):
print(line.decode().rstrip())
I get the output:
batch abort
confirm off
Connecting to ftp.website.com ...
Connected
Starting the session...
Session started.
Active session: [1] user#ftp.website.com
File or folder '\C:\mp4s\Sci-Fi' does not exist.
System Error. Code: 123.
The filename, directory name, or volume label syntax is incorrect
(A)bort, (R)etry, (S)kip, Ski(p) all: Abort
Which tells me that the put command isn't properly handling the spaces in the filename. What do I need to change to get this to work? Removing the double quotes around the path gives me a different error (it thinks I'm trying to upload multiple files).
I do not think you can use an array to provide arguments to WinSCP. The subprocess.Popen escapes double quotes in the arguments using backslash, what conflicts with double double-quotes escaping expected by WinSCP.
You will have to format the WinSCP command-line on your own:
args = "\"C:\Program Files (x86)\WinSCP\WinSCP.com\" /ini=nul " + \
f"/command \"open ftp://user:pass#ftp.website.com\" \"put \"\"{path}\"\"\" exit"
(note that I've omitted your option commands, as they are defaults anyway)

Execution error when Passing arguments to a python script using os.system. The script takes sys.argv arguments

I have tried to execute a simple python command from cmd like C:\Users> stat.py < swagger.yaml > output.html, which executes stat.py by taking swagger.yaml as input argument and generates output.html file and it worked fine in cmd. But now i want to execute my stat.py file through another python file demo.py by passing the values swagger.yaml and output.html as sys.argv[0] and sys.argv[1] inside demo.py.
my command from cmd C:\Users> demo.py swagger.yaml output.html and my demo.py file is as follows..
# my demo.py file ....
import os
import sys
os.system('stat.py < sys.argv[1] > sys.argv[2]')
error - the system can not find the file specified.
Why i am getting this error and please any help to resolve it ..
Inside a normal string, no variable interpretation is applied. So you literally asked to read from a file named sys.argv[1] (possibly sys.argv1 if the file exists, thanks to shell globbing), and write to a file named sys.argv[2].
If you want to use the values sys.argv in your script, you need to format them into the string, e.g. with f-strings (modern Python 3.6 or so only):
os.system(f'stat.py < {sys.argv[1]} > {sys.argv[2]}') # Note f at beginning of literal
or on older Python 2.7, with str.format:
os.system('stat.py < {} > {}'.format(sys.argv[1], sys.argv[2]))
Note that however you slice it, this is dangerous; os.system is launching this in a shell, and arguments that contain shell metacharacters will be interpreted as such. It can't do anything the user didn't already have permission to do, but small mistakes by the user could dramatically change the behavior of the program. If you want to do this properly/safely, use subprocess, open the files yourself, and pass them in explicitly as stdin/stdout:
with open(sys.argv[1], 'rb') as infile, open(sys.argv[2], 'wb') as outfile:
subprocess.run(['stat.py'], stdin=infile, stdout=outfile)
This ensures the files can be opened in the first place before launching the process, doesn't allow the shell to interpret anything, and avoids the (minor) expense of launching a shell at all. It's also going to give you more useful errors if opening the files fails.

How to fix "'bool' object is not iterable" error when running a UNIX command on a directory of files in python

I am trying to run a list of files in a directory through a UNIX executable using a python. I would the output of the executable for each file written to a different directory but retaining the original filename.
I am using python 2.7 so using the subprocess.call method. I am getting an error that says "'bool' object is not iterable" which I am guessing is due to the part where I am trying to write the output files as when I run the following script through the console I get an expected output specific to the executable within the console window:
import subprocess
import os
for inp in os.listdir('/path/to/input/directory/'):
subprocess.call(['/path/to/UNIX/executable', inp])
My code is currently this:
import subprocess
import os
for inp in os.listdir('/path/to/input/directory/'):
out = ['/path/to/output/directory/%s' % inp]
subprocess.call(['/path/to/UNIX/executable', inp] > out)
However, this second lot of code returns the "'bool' is not iterable" error.
I'm guessing the solution is pretty trivial as it is not a complicated task however, as a beginner, I do not know where to start!
SOLVED: following #barak-itkin's answer, for those who may stumble across this issue in the future, the code ran successfully using the following:
import subprocess
import os
for inp in os.listdir('/path/to/input/directory/'):
with open('/path/to/output/directory/%s' % inp, 'w') as out_file:
subprocess.call(['/path/to/UNIX/executable', inp], stdout=out_file)
To write the output of a subprocess.call to a file, you would need to either use the > path/to/out as part of the command itself, or to do it "properly" by specifying the file to which the output should go:
# Option 1:
# Specify that the command is using a "shell" syntax, meaning that
# things like output redirection (such as with ">") should be handled
# by the shell that will evaluate the command
subprocess.call('my_command arg1 arg2 > /path/to/out', shell=True)
# Option 2:
# Open the file to which you want to write the output, and then specify
# the `stdout` parameter to be that file
with open('/path/to/out', 'w') as out_file:
subprocess.call(['my_command', 'arg1', 'arg2'], stdout=out_file)
Does this work for you?

Categories