os.system to invoke an exe which lies in a dir whose name contains whitespace

Question:

My code is simply as follows:

file = 'C:\Exe\First Version\filename.exe'
os.system(file)

When I run this program, a Windows error is raised: can't find the file specified.

I found out the problem has to with the whitespace in the middle of “First Version”. How could I find a way to circumvent the problem?

P.S.: what if the variable ‘file’ is being passed as an argument into another function?

Asked By: Synapse

||

Answers:

Try enclosing it with double quotes.

file = '"C:\Exe\First Version\filename.exe"'
os.system(file)
Answered By: Doug

You can use the short name of the file which has spaces in its name.

file = 'C:\Exe\FirstV~1\filename.exe'
os.system(file)
Answered By: Tugrul Ates

Putting quotes around the path will work:

file = 'C:\Exe\First Version\filename.exe'
os.system('"' + file + '"')

but a better solution is to use the subprocess module instead:

import subprocess
file = 'C:\Exe\First Version\filename.exe'
subprocess.call([file])
Answered By: MRAB

I used this:

import subprocess, shlex
mycmd='"C:\Program Files\7-Zip\7z" x "D:\my archive.7z" -o"D:\extract folder" -aou'
subprocess.run(shlex.split(mycmd))
Answered By: abicorios

It is true that os.system will launch a binary which has spaces in the path by wrapping that path in quotes. (That should be a pretty obvious solution if you are accustom to using a terminal.) By itself, however, that doesn’t resolve the more painful problem with this function… Once you do that, you can then run into troubles adding arguments to your command! (Ahh!)

All current recommendations are to use the subprocess module now instead of this old, frowned upon function. One can also use shlx to convert flat strings into lists for those subprocess functions. I’ve encountered issues or pains with those methods too, which I won’t ramble on about… Also, it’s sometimes just easier use os.system when all you want is a thin wrapper over the shell, which implicitly displays the output streams on the console, works synchronously, etc. I sure wish there was a built-in function to execute a command on the shell like this, with absolutely 0 parsing, wrapping, abstracting

Since there is no built-in without "filters", here’s my solution patch for os.system. This is lifted from an open source library of mine. This has been tested on Windows, Mac, and Ubuntu Linux. I’m aware it’s not 100% foolproof, and it’s more involved than one would hop for, but it’s not too bad.

When you call this _system() wrapper (passing a string to execute), just surround your long path in quotes and include any arguments that needs with or without quotes as well (i.e. exactly as you would enter the command in a terminal!). On the first "token" in the command, this will remove the quotes and escape spaces in the path on Mac or Linux. On Windows, it uses the "short name" by actually resolving what that is on the given environment. That part of the code is a bit tricky. Basically it uses a batch mechanism for the name resolution, and it sends the results back over stderr for the purpose of parsing what you’d get otherwise for the Popen() results on stdout.

I think I included all the imports and defines that you’ll need. If I missed any (copying and pasting spinets of source), let me know.

from os import system, getcwd, chdir
from subprocess import Popen, PIPE

import platform
__plat = platform.system()
IS_WINDOWS = __plat == "Windows"
IS_LINUX   = __plat == "Linux"
IS_MACOS   = __plat == "Darwin"

__SCRUB_CMD_TMPL = "{0}{1}"
__DBL_QUOTE      = '"'
__SPACE          = ' '
__ESC_SPACE      = '\ '
if IS_WINDOWS :        
    __BATCH_RUN_AND_RETURN_CMD = ["cmd","/K"] # simply assuming cmd is on the system path... 
    __BATCH_ONE_LINER_TMPLT    = "{0} 1>&2n" # the newline triggers execution when piped in via stdin
    __BATCH_ESCAPE_PATH_TMPLT  = 'for %A in ("{0}") do @echo %~sA' 
    from subprocess import STARTUPINFO, STARTF_USESHOWWINDOW
    __BATCH_ONE_LINER_STARTUPINFO = STARTUPINFO()
    __BATCH_ONE_LINER_STARTUPINFO.dwFlags |= STARTF_USESHOWWINDOW 

def _system( cmd, wrkDir=None ):
    if wrkDir is not None:
        initWrkDir = getcwd()
        print( 'cd "%s"' % (wrkDir,) )
        chdir( wrkDir  )
    cmd = __scrubSystemCmd( cmd )        
    print( cmd )
    system( cmd ) 
    print('')
    if wrkDir is not None: chdir( initWrkDir )

def __scrubSystemCmd( cmd ):
    """
    os.system is more convenient than the newer subprocess functions
    when the intention is to act as very thin wrapper over the shell. 
    There is just one MAJOR problem with it: 
    If the first character in the command is a quote (to escape a long path
    to the binary you are executing), then the limited (undesirable) parsing 
    built into the function can all fall apart.  So, this scrub function
    solves that...  
    """    
    if not cmd.startswith( __DBL_QUOTE ): return cmd
    cmdParts    = cmd[1:].split( __DBL_QUOTE )
    safeBinPath = _escapePath( cmdParts[0] )
    args        = __DBL_QUOTE.join( cmdParts[1:] ) # (the leading space will remain)
    return __SCRUB_CMD_TMPL.format( safeBinPath, args ) 
                 
def _escapePath( path ):
    if not IS_WINDOWS: return path.replace(__SPACE, __ESC_SPACE)     
    return( path if __SPACE not in path else        
            __batchOneLinerOutput( __BATCH_ESCAPE_PATH_TMPLT.format(path) ) )    

def __batchOneLinerOutput( batch ):
    cmd = __BATCH_ONE_LINER_TMPLT.format( batch )
    p = Popen( __BATCH_RUN_AND_RETURN_CMD, shell=False, 
               startupinfo=__BATCH_ONE_LINER_STARTUPINFO,
               stdin=PIPE, stdout=PIPE, stderr=PIPE )    
    # pipe cmd to stdin, return stderr, minus a trailing newline
    return p.communicate( cmd )[1].rstrip()  

UPDATE

A better trick for the Windows context dawned on me recently. You don’t need any of the conversion to short file name, or an escape sequence for a space. All you need to do is thwart the Python source which throws a monkey wrench into this by checking if the first character in the command is a double quote. Well, on Windows cmd / Batch, you can prefix any command with an @ to indicate not to "echo" that line. So, if you simply slap that on the front of your command, there will no longer be a leading quote! You probably don’t want the command echoed either, so it’s an improvement in its self anyway.

Essentially, replace __scrubSystemCmd above with the following. If you want, you could drop all that code which gets the short file name (which is like half the code I orginally posted!)…

if IS_WINDOWS: 
    ...
    __NO_ECHO_PREFIX = "@"

def __scrubSystemCmd( cmd ):
    if IS_WINDOWS: 
        if not cmd.startswith( __NO_ECHO_PREFIX ): 
            return __NO_ECHO_PREFIX + cmd
    elif not cmd.startswith( __DBL_QUOTE ): return cmd
    cmdParts    = cmd[1:].split( __DBL_QUOTE )
    safeBinPath = _escapePath( cmdParts[0] )
    args        = __DBL_QUOTE.join( cmdParts[1:] ) # (the leading space will remain)
    return __SCRUB_CMD_TMPL.format( safeBinPath, args ) 
Answered By: BuvinJ
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.