gsscoder / commandline

Terse syntax C# command line parser for .NET with F# support
1.63k stars 293 forks source link

Commandline arguments with quoted paths ending in \ fail, e.g. "C:\" "D:\" #473

Closed ptr727 closed 6 months ago

ptr727 commented 7 years ago

If a commandline includes a quoted path ending in a \, the arguments are incorrectly merged.

E.g.

Foo.exe --ProcessFolders --MonitorFolders --UseMediaInfoFiles --Folders "W:\" "Y:\"

static int Main(string[] args)
[0] = "--ProcessFolders"
[1] = "--MonitorFolders"
[2] = "--UseMediaInfoFiles"
[3] = "--Folders"
[4] = "W:\" Y:\"" <-- Note the arguments got merged because the \" was incorrectly parsed

Environment.CommandLine = 
"\"C:\\Users\\...\\bin\\Debug\\Foo.exe\" --ProcessFolders --MonitorFolders --UseMediaInfoFiles --Folders \"W:\\\" \"Y:\\\""

The expected results were:
[0] = "--ProcessFolders"
[1] = "--MonitorFolders"
[2] = "--UseMediaInfoFiles"
[3] = "--Folders"
[4] = "W:\"
[5] = "Y:\"

This seems to be a general problem with C# console commandline parsing, and the docs for Environment.GetCommandLineArgs() say we need to use "c:\" to avoid this, which is unnatural.

In other commandline parsing classes this is not an issue for commandline args are treated as literal input, and split by space and by quoted strings.

Can you please create a constructor that does its own string parsing, i.e. instead of using the flawed console args, take the raw Environment.CommandLine value and split it, or take a default constructor that internally calls Environment.CommandLine?

celluj34 commented 7 years ago

Does it work like you expect if you escape the backslash?

pinkfloydx33 commented 7 years ago

This is a 'bug' with any .Net app that accepts command line arguments. If the string ends in \" then everything after it is merged (not just the next item). You can see this if you look at the args array in Main.. You'll have a much smaller array than expected. I noticed this with Ndesk.options a few months ago and traced the behavior to a NET/windows issue. GetCommandLineArgsNative is an internal call which obviously calls some native code to parses the command line arguments ( CommandLineToArgvW) which has special rules for quotes and slashes. These misparsed arguments are what get passed into the arguments array. Read here for more info on the particular issue. https://weblogs.asp.net/jongalloway/Command-Line-Confusion Check the Comments for a link to another blog post which in turn has a lot of details in ITS comments (not so much the blog itself)

ptr727 commented 7 years ago

The fix should be pretty simple to include in the mainline code:

            // TODO : Quoted paths ending in a \ fail to parse properly, use our own parser for now
            // https://github.com/gsscoder/commandline/issues/473
            ParserResult<Options> result = parser.ParseArguments<Options>(Tools.GetCommandlineArgs());
            if (result.Tag == ParserResultType.NotParsed)
            {
                Tools.WriteLineError("Failed to parse commandline.");
                return -1;
            }
            Options options = (((Parsed<Options>)result).Value);

        // https://stackoverflow.com/questions/298830/split-string-containing-command-line-parameters-into-string-in-c-sharp
        public static string[] ParseArguments(string commandLine)
        {
            char[] parmChars = commandLine.ToCharArray();
            bool inQuote = false;
            for (int index = 0; index < parmChars.Length; index++)
            {
                if (parmChars[index] == '"')
                    inQuote = !inQuote;
                if (!inQuote && parmChars[index] == ' ')
                    parmChars[index] = '\n';
            }
            return (new string(parmChars)).Split('\n');
        }

        public static string[] GetCommandlineArgs()
        {
            // Split the arguments
            string[] args = ParseArguments(Environment.CommandLine);

            // Strip the process path from the lsit of arguments
            string[] argsex = new string[args.Length - 1];
            Array.Copy(args, 1, argsex, 0, argsex.Length);
            return argsex;
        }
natemcmaster commented 5 years ago

This looks like a common mistake I've made dozens of times with Command Prompt. Windows itself does the argument splitting, and it is interpreting \" to mean "oh, you wanted to escape the backslash. If I remember right, you can get around this with either of these invocations:

foo.exe "W:\\" "X:\\"
foo.exe "W:^\" "X:^\"

Update was wrong about the second one. Just the first one works. See also https://github.com/natemcmaster/CommandLineUtils/issues/220#issuecomment-482453664

ptr727 commented 6 months ago

Abandon