3

I am running python 3.13.5 via PowerShell in windows 10. I can successfully run the folowing command, and now want to run it with a python script & 'D:\realesrgan\realesrgan-ncnn-vulkan.exe' -i 'D:\test.png' -o 'D:\output.png'. How do you do this?

Below is the very long chain of gotcha's I have ran into trying to do this. I am posting this for others searching the internet as I feel like I am not the only one who has encountered this.

I tried:

import subprocess
args = ["& 'D:\realesrgan\realesrgan-ncnn-vulkan.exe' -i 'D:\test.png' -o 'D:\output.png'"]
result = subprocess.run(args)

And got the error FileNotFoundError: [WinError 2] The system cannot find the file specified because I needed to call it with shell = True. So I tried:

import subprocess
args = ["& 'D:\realesrgan\realesrgan-ncnn-vulkan.exe' -i 'D:\test.png' -o 'D:\output.png'"]
result = subprocess.run(args, shell = True)

And got the error The filename, directory name, or volume label syntax is incorrect. because I needed to use double slashes \\ and break it into a list at the spaces. So I tried:

import subprocess
args = ["&", "'D:\\realesrgan\\realesrgan-ncnn-vulkan.exe'", "-i", "'D:\\test.png'", "-o", "'D:\\output.png'"]
result = subprocess.run(args, shell = True)

And got the error & was unexpected at this time. because subprocess runs on the command prompt not the PowerShell. So I learned I needed to remove the & and also swap the single quotes ' to double quotes " so I tried:

import subprocess
args = ['"D:\\realesrgan\\realesrgan-ncnn-vulkan.exe"', '-i', '"D:\\test.png"', '-o', '"D:\\output.png"']
result = subprocess.run(args, shell = True)

And got the error The filename, directory name, or volume label syntax is incorrect.. What is the next gotcha that I am missing? At this point I know the command "D:\\realesrgan\\realesrgan-ncnn-vulkan.exe" -i "D:\\test.png" -o "D:\\output.png" works on the command prompt. I once got the .exe to register but it dropped the options I was passing in and I can't recreate the code that made that happen. There might be something with using subprocess.call() instead of subprocess.run()?

2 Answers 2

6

subprocess.run() runs commands on the command prompt and NOT the PowerShell so you need a valid command prompt command. To run the command prompt command "D:\\realesrgan\\realesrgan-ncnn-vulkan.exe" -i "D:\\test.png" -o "D:\\output.png" the code is:

import subprocess
args = ['D:\\realesrgan\\realesrgan-ncnn-vulkan.exe', '-i', 'D:\\test.png', '-o', 'D:\\output.png']
result = subprocess.run(args, shell = True)

The components of this are: in subprocess.run() use the shell = True option, use double slashes \\ in the strings, don't use & like the PowerShell uses, separate the different parts of the command into separate entries in a list, and don't include the double quotes "'s in the strings even though they are needed when typed into the command prompt.

Sign up to request clarification or add additional context in comments.

5 Comments

Note that the point of shell = True is to be able to submit a shell command line in full, as a single string. If you use an array, Python assembles it into a command line for you behind the scenes. Your array-based approach, given that your command is not using any shell features, is better suited to not using shell = True, i.e. to direct invocation of the target executable, which also makes the call more efficient.
Would you be able to provide an example of what that python code would look like?
The command line-as-a-single-string approach is shown in all the code snippets in my answer. To make your array approach shell-less, use result = subprocess.run(args), i.e. remove the shell = True argument (or use the default explicitly, shell = False)
Conversely, on Windows using an array does not absolve you of the need to ultimately provide a single command-line that uses syntax that is appropriate for the target shell (by default cmd.exe aka "Command Prompt" on Windows). By relying on Python to do that for you, you not only give up control over the exact formulation of the command line (it happens to work as intended with cmd.exe, but can break if you configure PowerShell as the shell to use), you also obscure the underlying mechanism and make it more cumbersome to formulate the call.
Finally, a simple way to avoid the need to escape \ chars. as \\ is to use raw string literals (r'D:\realesrgan...')
2

Preface:

  • The next section shows how to configure Python's subprocess module to use PowerShell as the shell in shell=True calls (whereas the default is cmd.exe), which allows you to submit command lines using PowerShell syntax.

    • However, this may not be worth doing, given that:
      • (a) the configuration, due to being based on an environment variable, invariably affects the entire process and therefore potentially third-party calls; to avoid that you'd have to explicitly revert the configuration after your call.
      • (b) there are syntax pitfalls (see next section)
  • Sticking with the default shell, cmd.exe, the solution would look like this, using cmd.exe syntax - note that the point of shell=True is that you can pass a shell command line, i.e. a single string using the target shell's syntax (the double-quoting ("...") isn't strictly necessary here; it is used to demonstrate that it works):

      import subprocess
      # Formulate the command line using cmd.exe syntax; use double quotes only.
      cmdLine = r'D:\realesrgan\realesrgan-ncnn-vulkan.exe -i "D:\test.png" -o "D:\output.png"'
      result = subprocess.run(cmdLine, shell=True)
    
  • While the code in your own answer, which uses an array of arguments rather than a single command-line string on input, does work, it (a) only works as intended on Windows[1] and (b) is better suited to running without shell=True, given that it doesn't use any shell features; this also makes the call more efficient (because the target executable is called directly rather than via a shell child process).


Configuring PowerShell as the shell to use for subprocess-module calls on Windows:

  • Indeed, Python defaults to cmd.exe as the shell on Windows, though you can change that by modifying the COMSPEC environment variable.

    • Note that all subprocess module calls with shell=True in the same process will then have to use the newly configured shell's syntax; since third-party code may not be prepared to handle that, it's best to restore COMSPEC to its original value first.

    • Using PowerShell comes with non-obvious syntax pitfalls, however: see below.

  • The point of using a shell for execution (via shell=True) is to be able to pass a command line, i.e. a single string rather than an array of arguments.

    • If you do use an array, on Windows Python assembles its elements into a shell command line for you behind the scenes, as follows:
    • Elements that contain spaces are double-quoted on demand, i.e. enclosed in "..." (it is invariably double quotes that are used).
      • The (now possibly double-quoted) elements are then concatenated with spaces to form a single string, enclosed in "..." overall, and that single string is passed to the shell executable's /C option.
      • Note that no automatic escaping is performed on any embedded, element-individually enclosing " chars., which happens to work with cmd.exe, the default shell, but causes problems with PowerShell - see below.
      • Also, the array approach works fundamentally differently on Unix-like platforms.[1]

Thus, in order to be able to use PowerShell syntax with shell=True, you'll have to modify %COMSPEC% ($env:COMSPEC, in PowerShell terms) to point to powershell.exe, the Windows PowerShell CLI.

Here's an example using attrib.exe "C:\Program Files\*" as the command to run for simplicity, as
& 'attrib.exe' "C:\Program Files\*", i.e. using PowerShell-specific syntax; the mix of single- and double-quoting is deliberate, so as to point out a pitfall with double-quoting:

import os, subprocess

# Redefine %COMSPEC% to use the Windows PowerShell CLI, powershell.exe
os.environ['COMSPEC'] = os.path.expandvars(r'%SystemRoot%\System32\WindowsPowerShell\v1.0\powershell.exe')
# Formulate the command to run as a command line, as a single string.
# Note the need to \-escape the " chars.
cmdLine = r'''
& 'attrib.exe' \"C:\Program Files\*\"
'''
# Run it, via the configured shell, PowerShell:
result = subprocess.run(cmdLine, shell=True)

Caveat:

  • As shown above, if you use double-quoting ("...") in your PowerShell command line, you'll have to escape the " chars. as \" as seen by PowerShell on its process command line; this is not required for single-quoting ('...'), though note that single-quoted strings have different semantics in PowerShell.[2]

    • This escaping is required, because Python passes the command-line string value enclosed in "..." to the target shell's /c option without escaping " chars. embedded in the string.
      While this happens to work with cmd.exe due to its idiosyncratic parsing, PowerShell (sensibly) requires \-escaping of embedded " chars.
  • If you use regular double-quoted raw Python strings (r"...", as opposed to the triple-(single-)quoted form above), you can take advantage of the fact that the then necessary \-escaping of " chars. on the Python side is actually retained in the resulting string value; thus, you could have used the following in the above code, though it arguably obscures the need for escaping:

    cmdLine = r"& 'attrib.exe' \"C:\Program Files\*\""
    

[1] On Unix-like platforms, where /bin/sh is the default shell, rather than a single command line getting synthesized (as a single, "..."-enclosed argument containing the array elements - also individually "..."-enclosed if they contain spaces - passed to the shell executable's /C option), the arguments are passed individually to sh -c; the upshot is that unless your first array element is itself a piece of shell code that is designed to accept arguments, the remaining array elements (arguments) will in effect be ignored; a simple example: subprocess.run(['printf', '"%s\\n"', '1st argument', '2nd argument'], shell=True) fails, because the elements following printf are not passed to it; instead, you'd have to use subprocess.run(['printf "%s\\n" "$@"', '_', '1st argument', '2nd argument'], shell=True): it prints 1st argument and 2nd argument, due to these arguments being referenced via $@ as part of the first argument ('_', the true second argument, is a dummy argument; it becomes the value of $0).

[2] See about_Quoting_Rules and this answer for an overview of PowerShell's string literals.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.