Roland
Roland

Reputation: 5234

Python Subprocess or BAT script splits argument on equal sign

How do I add this: -param:abc=def as a SINGLE command line argument?

Module subprocess splits this up in TWO arguments by replacing the equal sign with a space.

Here is my python script:

import subprocess

pa=['test.bat', '--param:abc=def']
subprocess.run(pa)

Here is the test program test.bat:

@echo off
echo Test.bat
echo Arg0: %0
echo Arg1: %1
echo Arg2: %2
pause

and here the output:

Test.bat
Arg0: test.bat
Arg1: --param:abc
Arg2: def
Press any key to continue . . .

Because the equal sign is gone, the real app will not be started correctly. By the way, this problem also seems to happen when running on linux, with a sh script instead of a bat file.

I understand that removing the equal sign is a 'feature' in certain cases, e.g. with the argparse module, but in my case I need to keep the equal sign. Any help is appreciated!

Upvotes: 3

Views: 460

Answers (4)

fireattack
fireattack

Reputation: 141

While this is indeed mainly a batch file hell problem, Python's subprocess implementation detail is also relevant here. So I'd like to add some minor things to @jean-françois-fabre's great answer.

As said above, when using a BAT file directly, you can avoid the argument being split at equal sign by quoting it. And if you don't want the quotes later when you refer to the argument, you can simply use %~1 instead of doing string replacement manually.

E.g. in Test.bat:

@echo off
echo Test.bat
echo Arg0: %0
echo Arg1: %~1
echo Arg2: %~2
pause
D:\3>Test.bat "abc=def" 
Test.bat
Arg0: Test.bat
Arg1: abc=def
Arg2:
Press any key to continue . . .

(Note that it successfully got abc=def as an argument, and by using %~1 we removed the quotes too.)

Now, this is only half of the problem. The other half is on Python's side: subprocess.run(['Test.bat', 'abc=def']) will not work because Python won't quote argument with equal sign, and there is no way to force it to do it. (You can't manually add it, Python will escape it to \".)

So you have to manually construct the whole string and either use os.system as said above, or simply pass it (the string, instead of a list) into subprocess.run() :

subprocess.run('Test.bat "abc=def"') 

Also note, never use string (instead of list) in subprocess.run() on unix-like system. It will cause issues. If you really want to, add shell=True (you can add it on Windows too).

In summary:

import subprocess
import os

subprocess.run(['Test.bat', 'abc=def']) # BAD
subprocess.run(['Test.bat', '"abc=def"']) # BAD

os.system('Test.bat "abc=def"') # OK
subprocess.run('Test.bat "abc=def"') # OK
subprocess.run('Test.bat "abc=def"', shell=True) # OK

Upvotes: 1

Roland
Roland

Reputation: 5234

As Jean-Francois said, this is caused by BAT file Hell:

C:\>test.bat -param:abc=def
Test.bat
Arg0: test.bat
Arg1: -param:abc
Arg2: def
Press any key to continue . . .

Powershell does a better job, test.ps1:

write-host "There are a total of $($args.count) arguments"
for ( $i = 0; $i -lt $args.count; $i++ ) {
    write-host "Argument  $i is $($args[$i])"
} 

Running from the DOS prompt:

PS C:\> .\test.ps1 --params:abc=def
There are a total of 1 arguments
Argument  0 is --params:abc=def

Conclusion:

There is nothing wrong with Python.

Because the app that I need to start from Python has a BAT startup script, I have to replace that by a Powershell version *.ps1 script.

Upvotes: 0

J Muzhen
J Muzhen

Reputation: 352

I just checked, and you can use the shlex package to parse the string before sending it to the shell:

import subprocess, shlex

pa = shlex.split('test.bat --param:abc=def')  # parse string into a list

subprocess.run(pa)

Some info on the shlex module: It allows us to split strings using shell-like syntax, which is useful when working with the shell/command line. (docs)

Hope this helps.

Upvotes: 0

Jean-François Fabre
Jean-François Fabre

Reputation: 140148

Welcome to .bat file hell

To preserve equal sign, you'll have to quote your argument (explained here Preserving "=" (equal) characters in batch file parameters) ('"--param:abc=def"'), but then subprocess will escape the quotes

Test.bat
Arg0: test.bat
Arg1: \"--param:abc=def\"
Arg2:

Good old os.system won't do that

import os

os.system('test.bat "--param:abc=def"')

result

Test.bat
Arg0: test.bat
Arg1: "--param:abc=def"
Arg2:

Damn, those quotes won't go off. Let's tweak the .bat script a little to remove them manually

@echo off
echo Test.bat
echo Arg0: %0
rem those 2 lines remove the quotes
set ARG1=%1
set ARG1=%ARG1:"=%

echo Arg1: %ARG1%
echo Arg2: %2

now it yields the proper result

Test.bat
Arg0: test.bat
Arg1: --param:abc=def
Arg2:

Alternatively, stick to subprocess and remove quotes AND backslashes.

Upvotes: 1

Related Questions