[ACCEPTED]-How to accept command-line args ending in backslash-command-line

Accepted answer
Score: 14

That's likely the shell treating \ as an 5 escape character, and thus escaping the 4 character. So the shell sends \" as " (because 3 it thinks you are trying to escape the double 2 quote). The solution is to escape the escape 1 character, like so: $ python args.py "hello\world\\".

Score: 9

The Microsoft Parameter Parsing Rules

These are the rules for parsing a command 3 line passed by CreateProcess() to a program 2 written in C/C++:

  1. Parameters are always separated by a space or tab (multiple spaces/tabs OK)
  2. If the parameter does not contain any spaces, tabs, or double quotes, then all the characters in the parameter are accepted as is (there is no need to enclose the parameter in double quotes).
  3. Enclose spaces and tabs in a double quoted part
  4. A double quoted part can be anywhere within a parameter
  5. 2n backslashes followed by a " produce n backslashes + start/end double quoted part
  6. 2n+1 backslashes followed by a " produce n backslashes + a literal quotation mark
  7. n backslashes not followed by a quotation mark produce n backslashes
  8. If a closing " is followed immediately by another ", the 2nd " is accepted literally and added to the parameter (This is the undocumented rule.)

For a detailed and clear 1 description see http://www.daviddeley.com/autohotkey/parameters/parameters.htm#WINCRULESDOC

Score: 8

The backslash at the end is interpreted 15 as the start of an escape sequence, in this 14 case a literal double quote character. I 13 had a similar problem with handling environment 12 parameters containing a path that sometimes 11 ended with a \ and sometimes didn't.
The 10 solution I came up with was to always insert 9 a space at the end of the path string when 8 calling the executable. My executable then 7 uses the directory path with the slash and 6 a space at the end, which gets ignored. You 5 could possibly trim the path within the 4 program if it causes you issues.

If %SlashPath% = "hello\"

python args.py "%SlashPath% " world "any cpu"
Arguments:
[0] = args.py
[1] = hello\ 
[2] = world
[3] = any cpu

If 3 %SlashPath% = "hello"

python args.py "%SlashPath% " world "any cpu"
Arguments:
[0] = args.py
[1] = hello 
[2] = world
[3] = any cpu

Hopefully this will 2 give you some ideas of how to get around 1 your problem.

Score: 2

The backslash 'escapes' the character following 9 it. This means that the closing quotation 8 marks become a part of the argument, and 7 don't actually terminate the string.

This 6 is the behaviour of the shell you're using 5 (presumably bash or similar), not Python 4 (although you can escape characters within 3 Python strings, too).

The solution is to 2 escape the backslashes:

python args.py "hello\world\\"

Your Python script 1 should then function as you expect it to.

Score: 1

The backslash (\) is escaping the ". That's 1 all. That is how it is supposed to work.

Score: 1

If this is on Windows, then you are not 7 using a standard Windows command prompt 6 (or shell). This must be bash doing this. The 5 Windows command prompt doesn't treat backslash 4 as an escape character (since it's the file 3 path separator).

Extra trivia point: the 2 quoting character in Windows command prompts 1 is caret: ^

Score: 0

When the user passes your function a string 22 "hello\", regardless of what their intention 21 was, they sent the actual string hello", just 20 like if a user passed a filepath like "temp\table" what 19 they have really typed, intentionally or 18 not, is "temp able" (tab in the middle).

This 17 being said, a solution to this problem means 16 that if a user inputs "temp\table" and honestly 15 means "temp able", you are going to process 14 this into "temp\table" and now you've programmatically 13 destroyed the users input.

With this warning 12 in mind, if you still want to do this, you 11 can look for the string representation of 10 these escaped-characters and replace them. As 9 a really easy example, something like this:

def allow_tabs(str_w_tab):
    str_w_tab.replace('\t','\\t')
    print str_w_tab

Now 8 if you want to handle all the other escape 7 characters, you'll have to do something 6 similar for each one. As for being able 5 to do this for the example: "hello\", the 4 user passed you the string hello", and whether 3 they intended to or not, they never closed 2 the double-quote, so this is what your program 1 sees.

Score: 0

On 'nix based systems, this is a fundamental 27 shell limitation, as others have said here. So, just 26 suck it up. That said, it's really not 25 that important because you don't often need 24 backslashes in arguments on those platforms.

On 23 Windows, however, backslashes are of critical value! A 22 path ending in one would explicitly denote 21 a directory vs a file. I have seen the 20 documentation for MS C (see: https://docs.microsoft.com/en-us/previous-versions/17w5ykft(v=vs.85) ), and within 19 the Python source (e.g. in subprocess.list2cmd https://github.com/python/cpython/blob/master/Lib/subprocess.py), explaining 18 this problem with quoting a process argument 17 and have it not able to end with a backslash. So, I 16 forgive the Python developers for keeping 15 the logic the same - but not the MS C ones! This 14 is not a cmd.exe shell issue or a universal 13 limitation for arguments in Windows! (The 12 caret ^ is the equivalent escape character 11 in that natural shell.)

Batch Example (test.bat):

@echo off
echo 0: %0 
echo 1: %1 
echo 2: %2
echo 3: %3 

Now execute it (via 10 cmd.exe):

test.bat -t "C:\test\this path\" -v

Yields:

0: test.bat
1: -t
2: "C:\test\this path\"
3: -v

As you can see - a simple 9 batch file implicitly understands what we 8 want!

But... let's see what happens in Python, when 7 using the standard argparse module (https://docs.python.org/3/library/argparse.html), which is 6 intertwined with sys.argv initial parsing by default:

broken_args.py

import os
import argparse # pip install argparse

parser = argparse.ArgumentParser( epilog="DEMO HELP EPILOG" ) 
parser.add_argument( '-v', '--verbose', default=False, action='store_true', 
                     help='enable verbose output' )
parser.add_argument( '-t', '--target', default=None,
                     help='target directory' )                           
args = parser.parse_args()                       
print( "verbose: %s" % (args.verbose,) )
print( "target: %s" % (os.path.normpath( args.target ),) )

Test 5 that:

python broken_args.py -t "C:\test\this path\" -v

Yields these bad results:

verbose: False
target: C:\test\this path" -v

And so, here's 4 how I solved this. The key "trick" is 3 first fetching the full, raw command line 2 for the process via the Windows api:

fixed_args.py

import sys, os, shlex
import argparse # pip install argparse

IS_WINDOWS = sys.platform.startswith( 'win' )
IS_FROZEN  = getattr( sys, 'frozen', False )
    
class CustomArgumentParser( argparse.ArgumentParser ):
    if IS_WINDOWS:
        # override
        def parse_args( self ):
            def rawCommandLine():
                from ctypes.wintypes import LPWSTR
                from ctypes import windll
                Kernel32 = windll.Kernel32
                GetCommandLineW = Kernel32.GetCommandLineW
                GetCommandLineW.argtypes = ()
                GetCommandLineW.restype  = LPWSTR
                return GetCommandLineW()                            
            NIX_PATH_SEP = '/'                
            commandLine = rawCommandLine().replace( os.sep, NIX_PATH_SEP )
            skipArgCount = 1 if IS_FROZEN else 2
            args = shlex.split( commandLine )[skipArgCount:]        
            return argparse.ArgumentParser.parse_args( self, args )
 
parser = CustomArgumentParser( epilog="DEMO HELP EPILOG" ) 
parser.add_argument( '-v', '--verbose', default=False, action='store_true', 
                     help='enable verbose output' )
parser.add_argument( '-t', '--target', default=None,
                     help='target directory' )                           
args = parser.parse_args()                       
print( "verbose: %s" % (args.verbose,) )
print( "target: %s" % (os.path.normpath( args.target ),) )

Confirm 1 the fix:

python fixed_args.py -t "C:\test\this path\" -v

Yields these good results:

verbose: True
target: C:\test\this path

More Related questions