Windows subprocess.Popen a batch file without shel

2019-05-06 19:46发布

问题:

I have a function that runs lessc (installed with npm install -g less):

>>> import subprocess
>>> subprocess.Popen(['lessc'])
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "C:\Python27\lib\subprocess.py", line 679, in __init__
    errread, errwrite)
  File "C:\Python27\lib\subprocess.py", line 896, in _execute_child
    startupinfo)
WindowsError: [Error 2] The system cannot find the file specified

Unfortunately, it doesn't work unless I add shell=True:

>>> subprocess.Popen(['lessc'], shell=True)
<subprocess.Popen object at 0x01F619D0>

What can I do to make lessc run without using shell=True?

回答1:

Change the file to lessc.bat, or create .bat file that calls lessc. That way the file will be recognized by Windows as a batch file and will be executed properly.

You may also need to set cwd in addition to this depending on where the .bat file is.



回答2:

From both https://docs.python.org/3/library/subprocess.html#subprocess.Popen and https://docs.python.org/2/library/subprocess.html#subprocess.Popen:

You do not need shell=True to run a batch file or console-based executable.

as already cited by @JBernardo.

So, lets try:

where lessc actually tells

C:\Users\myname\AppData\Roaming\npm\lessc
C:\Users\myname\AppData\Roaming\npm\lessc.cmd

That means, the file to execute is lessc.cmd, not some .bat file. And indeed:

>>> import subprocess
>>> subprocess.Popen([r'C:\Users\myname\AppData\Roaming\npm\lessc.cmd'])
<subprocess.Popen object at 0x035BA070>
>>> lessc: no input files

usage: lessc [option option=parameter ...] <source> [destination]

So, this does work if you specify the full path. I assume there was a typo involved when you had this experience. May be you wrote .bat instead of .cmd?


If you don't want to patch the full path of lessc into your script, you can bake yourself a where:

import plaform
import os

def where(file_name):
    # inspired by http://nedbatchelder.com/code/utilities/wh.py
    # see also: http://stackoverflow.com/questions/11210104/
    path_sep = ":" if platform.system() == "Linux" else ";"
    path_ext = [''] if platform.system() == "Linux" or '.' in file_name else os.environ["PATHEXT"].split(path_sep)
    for d in os.environ["PATH"].split(path_sep):
        for e in path_ext:
            file_path = os.path.join(d, file_name + e)
            if os.path.exists(file_path):
                return file_path
    raise Exception(file_name + " not found")

Then you can write:

import subprocess
subprocess.Popen([where('lessc')])