Split string containing command-line parameters into string[] in C#
Asked Answered
A

27

105

I have a single string that contains the command-line parameters to be passed to another executable and I need to extract the string[] containing the individual parameters in the same way that C# would if the commands had been specified on the command-line. The string[] will be used when executing another assemblies entry-point via reflection.

Is there a standard function for this? Or is there a preferred method (regex?) for splitting the parameters correctly? It must handle '"' delimited strings that may contain spaces correctly, so I can't just split on ' '.

Example string:

string parameterString = @"/src:""C:\tmp\Some Folder\Sub Folder"" /users:""[email protected]"" tasks:""SomeTask,Some Other Task"" -someParam foo";

Example result:

string[] parameterArray = new string[] { 
  @"/src:C:\tmp\Some Folder\Sub Folder",
  @"/users:[email protected]",
  @"tasks:SomeTask,Some Other Task",
  @"-someParam",
  @"foo"
};

I do not need a command-line parsing library, just a way to get the String[] that should be generated.

Update: I had to change the expected result to match what is actually generated by C# (removed the extra "'s in the split strings)

Apples answered 18/11, 2008 at 14:10 Comment(4)
Google says: C#/.NET Command Line Arguments ParserKeepsake
Every time someone responds, you seem to have an objection based on material not in your post. I suggest that you update your post with this material. You may get better answers.Grecism
Good question, looking for the same. Was hoping to find someone say "hey .net exposes that here..." :) If I come across that at some point, I'll post it here, even though this is like 6 years old. Still a valid question!Prostyle
I've created a purely managed version in an answer below as I needed this function, too.Hilel
W
86

In addition to the good and pure managed solution by Earwicker, it may be worth mentioning, for sake of completeness, that Windows also provides the CommandLineToArgvW function for breaking up a string into an array of strings:

LPWSTR *CommandLineToArgvW(
    LPCWSTR lpCmdLine, int *pNumArgs);

Parses a Unicode command line string and returns an array of pointers to the command line arguments, along with a count of such arguments, in a way that is similar to the standard C run-time argv and argc values.

An example of calling this API from C# and unpacking the resulting string array in managed code can be found at, “Converting Command Line String to Args[] using CommandLineToArgvW() API.” Below is a slightly simpler version of the same code:

[DllImport("shell32.dll", SetLastError = true)]
static extern IntPtr CommandLineToArgvW(
    [MarshalAs(UnmanagedType.LPWStr)] string lpCmdLine, out int pNumArgs);

public static string[] CommandLineToArgs(string commandLine)
{
    int argc;
    var argv = CommandLineToArgvW(commandLine, out argc);        
    if (argv == IntPtr.Zero)
        throw new System.ComponentModel.Win32Exception();
    try
    {
        var args = new string[argc];
        for (var i = 0; i < args.Length; i++)
        {
            var p = Marshal.ReadIntPtr(argv, i * IntPtr.Size);
            args[i] = Marshal.PtrToStringUni(p);
        }

        return args;
    }
    finally
    {
        Marshal.FreeHGlobal(argv);
    }
}
Waylan answered 18/11, 2008 at 14:10 Comment(6)
This function requires that you escape the trailing backslash of a path inside quotes. "C:\Program Files\" must be "C:\Program Files\\" for this to function to parse the string correctly.Milissamilissent
It's also worth noting that CommandLineArgvW expects the first argument to be the program name, and the parsing magic applied isn't quite the same if one isn't passed in. You can fake it with something like: CommandLineToArgs("foo.exe " + commandLine).Skip(1).ToArray();Stephainestephan
For sake of completeness, MSVCRT does not use CommandLineToArgvW() to convert command line to argc/argv. It uses its own code, which is different. For example, try calling CreateProcess with this string: a"b c"d e f . In main() you'd get 3 arguments (as documented in MSDN), but CommandLineToArgvW()/GetCommandLineW() combo will give you 2.Sharla
OMG this is such a mess. typical MS soup. nothing is canonicalized, and never KISS is respected in MS world.Credenza
I posted a cross-platform version of the Microsoft translated MSVCRT implementation and a high-accuracy approximation using Regex. I know this is old, but hey - no body scrolls.Hellman
I translated C CommandLineToArgvW into a C# equivalent. Note that if you only parse args, you should prepend a fake command (like echo ) and slice the result from index 1, see @scott-wegner's comment.Taut
H
112

It annoys me that there's no function to split a string based on a function that examines each character. If there was, you could write it like this:

    public static IEnumerable<string> SplitCommandLine(string commandLine)
    {
        bool inQuotes = false;

        return commandLine.Split(c =>
                                 {
                                     if (c == '\"')
                                         inQuotes = !inQuotes;

                                     return !inQuotes && c == ' ';
                                 })
                          .Select(arg => arg.Trim().TrimMatchingQuotes('\"'))
                          .Where(arg => !string.IsNullOrEmpty(arg));
    }

Although having written that, why not write the necessary extension methods. Okay, you talked me into it...

Firstly, my own version of Split that takes a function that has to decide whether the specified character should split the string:

    public static IEnumerable<string> Split(this string str, 
                                            Func<char, bool> controller)
    {
        int nextPiece = 0;

        for (int c = 0; c < str.Length; c++)
        {
            if (controller(str[c]))
            {
                yield return str.Substring(nextPiece, c - nextPiece);
                nextPiece = c + 1;
            }
        }

        yield return str.Substring(nextPiece);
    }

It may yield some empty strings depending on the situation, but maybe that information will be useful in other cases, so I don't remove the empty entries in this function.

Secondly (and more mundanely) a little helper that will trim a matching pair of quotes from the start and end of a string. It's more fussy than the standard Trim method - it will only trim one character from each end, and it will not trim from just one end:

    public static string TrimMatchingQuotes(this string input, char quote)
    {
        if ((input.Length >= 2) && 
            (input[0] == quote) && (input[input.Length - 1] == quote))
            return input.Substring(1, input.Length - 2);

        return input;
    }

And I suppose you'll want some tests as well. Well, alright then. But this must be absolutely the last thing! First a helper function that compares the result of the split with the expected array contents:

    public static void Test(string cmdLine, params string[] args)
    {
        string[] split = SplitCommandLine(cmdLine).ToArray();

        Debug.Assert(split.Length == args.Length);

        for (int n = 0; n < split.Length; n++)
            Debug.Assert(split[n] == args[n]);
    }

Then I can write tests like this:

        Test("");
        Test("a", "a");
        Test(" abc ", "abc");
        Test("a b ", "a", "b");
        Test("a b \"c d\"", "a", "b", "c d");

Here's the test for your requirements:

        Test(@"/src:""C:\tmp\Some Folder\Sub Folder"" /users:""[email protected]"" tasks:""SomeTask,Some Other Task"" -someParam",
             @"/src:""C:\tmp\Some Folder\Sub Folder""", @"/users:""[email protected]""", @"tasks:""SomeTask,Some Other Task""", @"-someParam");

Note that the implementation has the extra feature that it will remove quotes around an argument if that makes sense (thanks to the TrimMatchingQuotes function). I believe that's part of the normal command-line interpretation.

Hogfish answered 18/11, 2008 at 14:10 Comment(7)
I had to un-mark this as the answer because I didn't have the right expected outputs. The actual output should not have the "'s in the final arrayApples
I come to Stack Overflow to get away from requirements that change all the time! :) You could use Replace("\"", "") instead of TrimMatchingQuotes() to get rid of all quotes. But Windows supports \" to allow a quote character to be passed through. My Split function can't do that.Hogfish
Nice one Earwicker :) Anton: This is the solution I was trying to describe to you in my earlier post, but Earwicker did a much better job in writitng it down ;) And also extened it a lot ;)Oppidan
a whitespace is not the only separating character for command line arguments, is it?Aloysius
@Louis Rhys - I'm not sure. If that is a concern it is pretty easy to solve: use char.IsWhiteSpace instead of == ' 'Hogfish
I think Test("a \"\"", "a", "") would fail (i.e. no way to pass an empty string as argument. The test on the string being empty should be done before the trimming of the quotes.Bourgeois
I think you made a mistake in your Select and Where logic. First you remove all quotes and then you remove all empty strings. This way you remove all empty strings, even those that have been quoted. It should be the other way around: first check for empty strings than remove the quotes. This way, empty strings that explicitly have been put into quotes, remain in the final result.Fluctuant
W
86

In addition to the good and pure managed solution by Earwicker, it may be worth mentioning, for sake of completeness, that Windows also provides the CommandLineToArgvW function for breaking up a string into an array of strings:

LPWSTR *CommandLineToArgvW(
    LPCWSTR lpCmdLine, int *pNumArgs);

Parses a Unicode command line string and returns an array of pointers to the command line arguments, along with a count of such arguments, in a way that is similar to the standard C run-time argv and argc values.

An example of calling this API from C# and unpacking the resulting string array in managed code can be found at, “Converting Command Line String to Args[] using CommandLineToArgvW() API.” Below is a slightly simpler version of the same code:

[DllImport("shell32.dll", SetLastError = true)]
static extern IntPtr CommandLineToArgvW(
    [MarshalAs(UnmanagedType.LPWStr)] string lpCmdLine, out int pNumArgs);

public static string[] CommandLineToArgs(string commandLine)
{
    int argc;
    var argv = CommandLineToArgvW(commandLine, out argc);        
    if (argv == IntPtr.Zero)
        throw new System.ComponentModel.Win32Exception();
    try
    {
        var args = new string[argc];
        for (var i = 0; i < args.Length; i++)
        {
            var p = Marshal.ReadIntPtr(argv, i * IntPtr.Size);
            args[i] = Marshal.PtrToStringUni(p);
        }

        return args;
    }
    finally
    {
        Marshal.FreeHGlobal(argv);
    }
}
Waylan answered 18/11, 2008 at 14:10 Comment(6)
This function requires that you escape the trailing backslash of a path inside quotes. "C:\Program Files\" must be "C:\Program Files\\" for this to function to parse the string correctly.Milissamilissent
It's also worth noting that CommandLineArgvW expects the first argument to be the program name, and the parsing magic applied isn't quite the same if one isn't passed in. You can fake it with something like: CommandLineToArgs("foo.exe " + commandLine).Skip(1).ToArray();Stephainestephan
For sake of completeness, MSVCRT does not use CommandLineToArgvW() to convert command line to argc/argv. It uses its own code, which is different. For example, try calling CreateProcess with this string: a"b c"d e f . In main() you'd get 3 arguments (as documented in MSDN), but CommandLineToArgvW()/GetCommandLineW() combo will give you 2.Sharla
OMG this is such a mess. typical MS soup. nothing is canonicalized, and never KISS is respected in MS world.Credenza
I posted a cross-platform version of the Microsoft translated MSVCRT implementation and a high-accuracy approximation using Regex. I know this is old, but hey - no body scrolls.Hellman
I translated C CommandLineToArgvW into a C# equivalent. Note that if you only parse args, you should prepend a fake command (like echo ) and slice the result from index 1, see @scott-wegner's comment.Taut
P
28

Because I wanted the same behavior as OP (split a string exactly the same as windows cmd would do it) I wrote a bunch of test cases and tested the here posted answers:

    Test( 0, m, "One",                    new[] { "One" });
    Test( 1, m, "One ",                   new[] { "One" });
    Test( 2, m, " One",                   new[] { "One" });
    Test( 3, m, " One ",                  new[] { "One" });
    Test( 4, m, "One Two",                new[] { "One", "Two" });
    Test( 5, m, "One  Two",               new[] { "One", "Two" });
    Test( 6, m, "One   Two",              new[] { "One", "Two" });
    Test( 7, m, "\"One Two\"",            new[] { "One Two" });
    Test( 8, m, "One \"Two Three\"",      new[] { "One", "Two Three" });
    Test( 9, m, "One \"Two Three\" Four", new[] { "One", "Two Three", "Four" });
    Test(10, m, "One=\"Two Three\" Four", new[] { "One=Two Three", "Four" });
    Test(11, m, "One\"Two Three\" Four",  new[] { "OneTwo Three", "Four" });
    Test(12, m, "One\"Two Three   Four",  new[] { "OneTwo Three   Four" });
    Test(13, m, "\"One Two\"",            new[] { "One Two" });
    Test(14, m, "One\" \"Two",            new[] { "One Two" });
    Test(15, m, "\"One\"  \"Two\"",       new[] { "One", "Two" });
    Test(16, m, "One\\\"  Two",           new[] { "One\"", "Two" });
    Test(17, m, "\\\"One\\\"  Two",       new[] { "\"One\"", "Two" });
    Test(18, m, "One\"",                  new[] { "One" });
    Test(19, m, "\"One",                  new[] { "One" });
    Test(20, m, "One \"\"",               new[] { "One", "" });
    Test(21, m, "One \"",                 new[] { "One", "" });
    Test(22, m, "1 A=\"B C\"=D 2",        new[] { "1", "A=B C=D", "2" });
    Test(23, m, "1 A=\"B \\\" C\"=D 2",   new[] { "1", "A=B \" C=D", "2" });
    Test(24, m, "1 \\A 2",                new[] { "1", "\\A", "2" });
    Test(25, m, "1 \\\" 2",               new[] { "1", "\"", "2" });
    Test(26, m, "1 \\\\\" 2",             new[] { "1", "\\\"", "2" });
    Test(27, m, "\"",                     new[] { "" });
    Test(28, m, "\\\"",                   new[] { "\"" });
    Test(29, m, "'A B'",                  new[] { "'A", "B'" });
    Test(30, m, "^",                      new[] { "^" });
    Test(31, m, "^A",                     new[] { "A" });
    Test(32, m, "^^",                     new[] { "^" });
    Test(33, m, "\\^^",                   new[] { "\\^" });
    Test(34, m, "^\\\\",                  new[] { "\\\\" });
    Test(35, m, "^\"A B\"",               new[] { "A B" });

    // Test cases Anton

    Test(36, m, @"/src:""C:\tmp\Some Folder\Sub Folder"" /users:""[email protected]"" tasks:""SomeTask,Some Other Task"" -someParam foo", new[] { @"/src:C:\tmp\Some Folder\Sub Folder", @"/users:[email protected]", @"tasks:SomeTask,Some Other Task", @"-someParam", @"foo" });

    // Test cases Daniel Earwicker 

    Test(37, m, "",            new string[] { });
    Test(38, m, "a",           new[] { "a" });
    Test(39, m, " abc ",       new[] { "abc" });
    Test(40, m, "a b ",        new[] { "a", "b" });
    Test(41, m, "a b \"c d\"", new[] { "a", "b", "c d" });

    // Test cases Fabio Iotti 

    Test(42, m, "this is a test ",    new[] { "this", "is", "a", "test" });
    Test(43, m, "this \"is a\" test", new[] { "this", "is a", "test" });

    // Test cases Kevin Thach

    Test(44, m, "\"C:\\Program Files\"",                       new[] { "C:\\Program Files" });
    Test(45, m, "\"He whispered to her \\\"I love you\\\".\"", new[] { "He whispered to her \"I love you\"." });

the "expected" value comes from directly testing it with cmd.exe on my machine (Win10 x64) and a simple print program:

static void Main(string[] args) => Console.Out.WriteLine($"Count := {args.Length}\n{string.Join("\n", args.Select((v,i) => $"[{i}] => '{v}'"))}");

These are the results:


Solution                      | Failed Tests
------------------------------|------------------------------------- 
Atif Aziz (749653)            | 2, 3, 10, 11, 12, 14, 16, 17, 18, 26, 28, 31, 32, 33, 34, 35, 36, 37, 39, 45
Jeffrey L Whitledge (298968)  | 1, 2, 3, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 25, 26, 27, 28, 31, 32, 33, 34, 35, 36, 37, 39, 40, 41, 42, 43, 44, 45
Daniel Earwicker (298990)     | 10, 11, 12, 14, 16, 17, 18, 19, 20, 21, 22, 23, 25, 26, 27, 28, 31, 32, 33, 34, 35, 36, 45
Anton (299795)                | 12, 16, 17, 18, 19, 21, 23, 25, 26, 27, 28, 31, 32, 33, 34, 35, 45
CS. (467313)                  | 12, 18, 19, 21, 27, 31, 32, 33, 34, 35
Vapour in the Alley (2132004) | 10, 11, 12, 14, 16, 17, 20, 21, 22, 23, 25, 26, 27, 28, 29, 31, 32, 33, 34, 35, 36, 45
Monoman (7774211)             | 14, 16, 17, 20, 21, 22, 23, 25, 26, 27, 28, 31, 32, 33, 34, 35, 45
Thomas Petersson (19091999)   | 2, 3, 10, 11, 12, 14, 16, 17, 18, 19, 20, 21, 22, 23, 25, 26, 27, 28, 31, 32, 33, 34, 35, 36, 39, 45
Fabio Iotti (19725880)        | 1, 2, 3, 7, 10, 11, 12, 13, 14, 15, 16, 17, 19, 21, 22, 23, 25, 26, 28, 29, 30, 35, 36, 37, 39, 40, 42, 44, 45
ygoe (23961658)               | 26, 31, 32, 33, 34, 35
Kevin Thach (24829691)        | 10, 11, 12, 14, 18, 19, 20, 21, 22, 23, 26, 27, 31, 32, 33, 34, 35, 36
Lucas De Jesus (31621370)     | 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45
HarryP (48008872)             | 24, 26, 31, 32, 33, 34, 35
TylerY86 (53290784)           | 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 25, 26, 27, 28, 31, 32, 33, 34, 35, 36, 41, 43, 44, 45
Louis Somers (55903304)       | 2, 3, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 25, 26, 27, 28, 29, 31, 32, 33, 34, 35, 36, 39, 41, 43, 44, 45
user2126375 (58233585)        | 5, 6, 15, 16, 17, 31, 32, 33, 34, 35
DilipNannaware (59131568)     | 1, 2, 3, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 25, 26, 27, 28, 31, 32, 33, 34, 35, 36, 37, 39, 40, 41, 42, 43, 44, 45
Mikescher (this)              | -

Because no answer seemed correct (at least based on my use case) here is my solution, it currently passes all test cases (but if anyone has additional (failing) corner cases please comment):

public static IEnumerable<string> SplitArgs(string commandLine)
{
    var result = new StringBuilder();

    var quoted = false;
    var escaped = false;
    var started = false;
    var allowcaret = false;
    for (int i = 0; i < commandLine.Length; i++)
    {
        var chr = commandLine[i];

        if (chr == '^' && !quoted)
        {
            if (allowcaret)
            {
                result.Append(chr);
                started = true;
                escaped = false;
                allowcaret = false;
            }
            else if (i + 1 < commandLine.Length && commandLine[i + 1] == '^')
            {
                allowcaret = true;
            }
            else if (i + 1 == commandLine.Length)
            {
                result.Append(chr);
                started = true;
                escaped = false;
            }
        }
        else if (escaped)
        {
            result.Append(chr);
            started = true;
            escaped = false;
        }
        else if (chr == '"')
        {
            quoted = !quoted;
            started = true;
        }
        else if (chr == '\\' && i + 1 < commandLine.Length && commandLine[i + 1] == '"')
        {
            escaped = true;
        }
        else if (chr == ' ' && !quoted)
        {
            if (started) yield return result.ToString();
            result.Clear();
            started = false;
        }
        else
        {
            result.Append(chr);
            started = true;
        }
    }

    if (started) yield return result.ToString();
}

The code I used to generate the test results can be found here

Packton answered 18/11, 2008 at 14:10 Comment(5)
Greatest answer here. Would you consider making a nuget package? ?Eckel
I posted a github repo with a personal contribution. This would allow public contributions to unit tests and implementations. @Packton would you like to be set as a repo contributor?Eckel
Wow... Outstanding testing job, thanks!Paxon
It doesn't seem to process " escaped as "". I modified your code accordingly. Please review.Paxon
@SergeWautier Hmm if I test it on my machine (Win10) two quotes dont escape two a single quote character (they get simply discarded or are an empty argument) So I don't think thats right. (You can test it with the print program from my post)Packton
P
27

The Windows command-line parser behaves just as you say, split on space unless there's a unclosed quote before it. I would recommend writing the parser yourself. Something like this maybe:

    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');
    }
Patois answered 18/11, 2008 at 14:10 Comment(5)
I ended up with the same thing, excepy I used .Split(new char[] { '\n' }, StringSplitOptions.RemoveEmptyEntries) in the final line in case there were extra ' 's between params. Seems to be working.Apples
I assume Windows must have a way to escape quotes in the parameters... this algorithm does not take that into account.Exuberate
Removing blank lines, removing outside quotes, and handling escaped quotes are left as an excersize for the reader.Patois
Char.IsWhiteSpace() could help hereLewellen
This solution is good if Arguments are separated by single space, but fails is arguments are separated by multiple spaces. Link to correct solution: https://mcmap.net/q/21989/-split-string-containing-command-line-parameters-into-string-in-cAndesine
M
14

I took the answer from Jeffrey L Whitledge and enhanced it a little.

It now supports both single and double quotes. You can use quotes in the parameters itself by using other typed quotes.

It also strips the quotes from the arguments since these do not contribute to the argument information.

    public static string[] SplitArguments(string commandLine)
    {
        var parmChars = commandLine.ToCharArray();
        var inSingleQuote = false;
        var inDoubleQuote = false;
        for (var index = 0; index < parmChars.Length; index++)
        {
            if (parmChars[index] == '"' && !inSingleQuote)
            {
                inDoubleQuote = !inDoubleQuote;
                parmChars[index] = '\n';
            }
            if (parmChars[index] == '\'' && !inDoubleQuote)
            {
                inSingleQuote = !inSingleQuote;
                parmChars[index] = '\n';
            }
            if (!inSingleQuote && !inDoubleQuote && parmChars[index] == ' ')
                parmChars[index] = '\n';
        }
        return (new string(parmChars)).Split(new[] { '\n' }, StringSplitOptions.RemoveEmptyEntries);
    }
Mistletoe answered 18/11, 2008 at 14:10 Comment(0)
B
8

The good and pure managed solution by Earwicker failed to handle arguments like this:

Test("\"He whispered to her \\\"I love you\\\".\"", "He whispered to her \"I love you\".");

It returned 3 elements:

"He whispered to her \"I
love
you\"."

So here is a fix to support the "quoted \"escape\" quote":

public static IEnumerable<string> SplitCommandLine(string commandLine)
{
    bool inQuotes = false;
    bool isEscaping = false;

    return commandLine.Split(c => {
        if (c == '\\' && !isEscaping) { isEscaping = true; return false; }

        if (c == '\"' && !isEscaping)
            inQuotes = !inQuotes;

        isEscaping = false;

        return !inQuotes && Char.IsWhiteSpace(c)/*c == ' '*/;
        })
        .Select(arg => arg.Trim().TrimMatchingQuotes('\"').Replace("\\\"", "\""))
        .Where(arg => !string.IsNullOrEmpty(arg));
}

Tested with 2 additional cases:

Test("\"C:\\Program Files\"", "C:\\Program Files");
Test("\"He whispered to her \\\"I love you\\\".\"", "He whispered to her \"I love you\".");

Also noted that the accepted answer by Atif Aziz which uses CommandLineToArgvW also failed. It returned 4 elements:

He whispered to her \ 
I 
love 
you". 

Hope this helps someone looking for such a solution in the future.

Bryonbryony answered 18/11, 2008 at 14:10 Comment(1)
Sorry for the necromancy but this solution still misses things like bla.exe aAAA"b\"ASDS\"c"dSADSD which results in aAAAb"ASDS"cdSADSD where this solution would output aAAA"b"ASDS"c"dSADSD. I might consider changing the TrimMatchingQuotes to a Regex("(?<!\\\\)\\\"") and use it like this.Cocoa
H
5

I like iterators, and nowadays LINQ makes IEnumerable<String> as easily usable as arrays of string, so my take following the spirit of Jeffrey L Whitledge's answer is (as a extension method to string):

public static IEnumerable<string> ParseArguments(this string commandLine)
{
    if (string.IsNullOrWhiteSpace(commandLine))
        yield break;

    var sb = new StringBuilder();
    bool inQuote = false;
    foreach (char c in commandLine) {
        if (c == '"' && !inQuote) {
            inQuote = true;
            continue;
        }

        if (c != '"' && !(char.IsWhiteSpace(c) && !inQuote)) {
            sb.Append(c);
            continue;
        }

        if (sb.Length > 0) {
            var result = sb.ToString();
            sb.Clear();
            inQuote = false;
            yield return result;
        }
    }

    if (sb.Length > 0)
        yield return sb.ToString();
}
Heshum answered 18/11, 2008 at 14:10 Comment(0)
G
4

Environment.GetCommandLineArgs()

Gadfly answered 18/11, 2008 at 14:10 Comment(2)
Useful - but this will only get you the command line args sent to the current process. The requirement was to get a string[] from a string "in the same way that C# would if the commands had been specified on the command-line". I guess we could use a decompiler to look at how MS implemented this though...Bushido
As Jon Galloway also found (weblogs.asp.net/jgalloway/archive/2006/09/13/…) a decompiler doesn't help much which brings us right back to Atif's answer (#299330)Bushido
C
3

There's a NuGet package which contains exactly the functionality you need:

Microsoft.CodeAnalysis.Common contains the class CommandLineParser with the method SplitCommandLineIntoArguments.

You use it like this:

using Microsoft.CodeAnalysis;
// [...]
var cli = @"/src:""C:\tmp\Some Folder\Sub Folder"" /users:""[email protected]"" tasks:""SomeTask,Some Other Task"" -someParam foo";
var cliArgs = CommandLineParser.SplitCommandLineIntoArguments(cli, true);

Console.WriteLine(string.Join('\n', cliArgs));
// prints out:
// /src:"C:\tmp\Some Folder\Sub Folder"
// /users:"[email protected]"
// tasks:"SomeTask,Some Other Task"
// -someParam
// foo
Comfort answered 18/11, 2008 at 14:10 Comment(1)
The source for that is here github.com/dotnet/roslyn/blob/v4.2.0/src/Compilers/Core/…Antic
H
3

Oh heck. It's all ... Eugh. But this is legit official. From Microsoft in C# for .NET Core, maybe windows only, maybe cross-platform, but MIT licensed.

Select tidbits, method declarations and notable comments;

internal static unsafe string[] InternalCreateCommandLine(bool includeArg0)
private static unsafe int SegmentCommandLine(char * pCmdLine, string[] argArray, bool includeArg0)
private static unsafe int ScanArgument0(ref char* psrc, char[] arg)
private static unsafe int ScanArgument(ref char* psrc, ref bool inquote, char[] arg)

-

// First, parse the program name (argv[0]). Argv[0] is parsed under special rules. Anything up to 
// the first whitespace outside a quoted subtring is accepted. Backslashes are treated as normal 
// characters.

-

// Rules: 2N backslashes + " ==> N backslashes and begin/end quote
//      2N+1 backslashes + " ==> N backslashes + literal "
//         N backslashes     ==> N backslashes

This is code ported to .NET Core from .NET Framework from what I assume is either the MSVC C library or CommandLineToArgvW.

Here's my half-hearted attempt at handling some of the shenanigans with Regular Expressions, and ignoring the argument zero bit. It's a little bit wizardy.

private static readonly Regex RxWinArgs
  = new Regex("([^\\s\"]+\"|((?<=\\s|^)(?!\"\"(?!\"))\")+)(\"\"|.*?)*\"[^\\s\"]*|[^\\s]+",
    RegexOptions.Compiled
    | RegexOptions.Singleline
    | RegexOptions.ExplicitCapture
    | RegexOptions.CultureInvariant);

internal static IEnumerable<string> ParseArgumentsWindows(string args) {
  var match = RxWinArgs.Match(args);

  while (match.Success) {
    yield return match.Value;
    match = match.NextMatch();
  }
}

Tested it a fair bit on wacky generated output. It's output matches a fair percentage of what the monkeys typed up and ran through CommandLineToArgvW.

Hellman answered 18/11, 2008 at 14:10 Comment(3)
github.com/dotnet/corert/blob/master/src/System.Private.CoreLib/… is deadInsecticide
Yeah looks like the C# version is dead. github.com/dotnet/runtime/blob/master/src/coreclr/src/utilcode/…Hellman
Limited time revival. pastebin.com/ajhrBS4tHellman
T
3

In your question you asked for a regex, and I am a big fan and user of them, so when I needed to do this same argument split as you, I wrote my own regex after googling around and not finding a simple solution. I like short solutions, so I made one and here it is:

            var re = @"\G(""((""""|[^""])+)""|(\S+)) *";
            var ms = Regex.Matches(CmdLine, re);
            var list = ms.Cast<Match>()
                         .Select(m => Regex.Replace(
                             m.Groups[2].Success
                                 ? m.Groups[2].Value
                                 : m.Groups[4].Value, @"""""", @"""")).ToArray();

It handles blanks and quotes inside quotation marks, and converts enclosed "" to ". Feel free to use the code!

Tancred answered 18/11, 2008 at 14:10 Comment(0)
D
2

This The Code Project article is what I've used in the past. It's a good bit of code, but it might work.

This MSDN article is the only thing I could find that explains how C# parses command line arguments.

Deville answered 18/11, 2008 at 14:10 Comment(2)
I tried reflector'ing into the C# library, but it goes to a native C++ call that I don't have the code for, and can't see any way to call without p-invoking it. I also do not want a command-line parsing library, I just want the string[].Apples
Reflecting .NET brought me nowhere as well. Looking into the Mono source code suggested that this argument splitting is not done by the CLR but rather already comes from the operating system. Think of the argc, argv parameters of the C main function. So there is nothing to reuse other than the OS API.Hilel
H
1

A purely managed solution might be helpful. There are too many "problem" comments for the WINAPI function and it's not available on other platforms. Here's my code that has a well-defined behaviour (that you can change if you like).

It should do the same as what .NET/Windows do when providing that string[] args parameter, and I've compared it with a number of "interesting" values.

This is a classic state-machine implementation that takes each single character from the input string and interprets it for the current state, producing output and a new state. The state is defined in the variables escape, inQuote, hadQuote and prevCh, and the output is collected in currentArg and args.

Some of the specialties that I've discovered by experiments on a real command prompt (Windows 7): \\ produces \, \" produces ", "" within a quoted range produces ".

The ^ character seems to be magical, too: it always disappears when not doubling it. Otherwise it has no effect on a real command line. My implementation does not support this, as I haven't found a pattern in this behaviour. Maybe somebody knows more about it.

Something that doesn't fit in this pattern is the following command:

cmd /c "argdump.exe "a b c""

The cmd command seems to catch the outer quotes and take the rest verbatim. There must be some special magic sauce in this.

I've done no benchmarks on my method, but consider it reasonably fast. It doesn't use Regex and doesn't do any string concatenation but instead uses a StringBuilder to collect the characters for an argument and puts them in a list.

/// <summary>
/// Reads command line arguments from a single string.
/// </summary>
/// <param name="argsString">The string that contains the entire command line.</param>
/// <returns>An array of the parsed arguments.</returns>
public string[] ReadArgs(string argsString)
{
    // Collects the split argument strings
    List<string> args = new List<string>();
    // Builds the current argument
    var currentArg = new StringBuilder();
    // Indicates whether the last character was a backslash escape character
    bool escape = false;
    // Indicates whether we're in a quoted range
    bool inQuote = false;
    // Indicates whether there were quotes in the current arguments
    bool hadQuote = false;
    // Remembers the previous character
    char prevCh = '\0';
    // Iterate all characters from the input string
    for (int i = 0; i < argsString.Length; i++)
    {
        char ch = argsString[i];
        if (ch == '\\' && !escape)
        {
            // Beginning of a backslash-escape sequence
            escape = true;
        }
        else if (ch == '\\' && escape)
        {
            // Double backslash, keep one
            currentArg.Append(ch);
            escape = false;
        }
        else if (ch == '"' && !escape)
        {
            // Toggle quoted range
            inQuote = !inQuote;
            hadQuote = true;
            if (inQuote && prevCh == '"')
            {
                // Doubled quote within a quoted range is like escaping
                currentArg.Append(ch);
            }
        }
        else if (ch == '"' && escape)
        {
            // Backslash-escaped quote, keep it
            currentArg.Append(ch);
            escape = false;
        }
        else if (char.IsWhiteSpace(ch) && !inQuote)
        {
            if (escape)
            {
                // Add pending escape char
                currentArg.Append('\\');
                escape = false;
            }
            // Accept empty arguments only if they are quoted
            if (currentArg.Length > 0 || hadQuote)
            {
                args.Add(currentArg.ToString());
            }
            // Reset for next argument
            currentArg.Clear();
            hadQuote = false;
        }
        else
        {
            if (escape)
            {
                // Add pending escape char
                currentArg.Append('\\');
                escape = false;
            }
            // Copy character from input, no special meaning
            currentArg.Append(ch);
        }
        prevCh = ch;
    }
    // Save last argument
    if (currentArg.Length > 0 || hadQuote)
    {
        args.Add(currentArg.ToString());
    }
    return args.ToArray();
}
Hilel answered 18/11, 2008 at 14:10 Comment(0)
A
1

Use:

public static string[] SplitArguments(string args) {
    char[] parmChars = args.ToCharArray();
    bool inSingleQuote = false;
    bool inDoubleQuote = false;
    bool escaped = false;
    bool lastSplitted = false;
    bool justSplitted = false;
    bool lastQuoted = false;
    bool justQuoted = false;

    int i, j;

    for(i=0, j=0; i<parmChars.Length; i++, j++) {
        parmChars[j] = parmChars[i];

        if(!escaped) {
            if(parmChars[i] == '^') {
                escaped = true;
                j--;
            } else if(parmChars[i] == '"' && !inSingleQuote) {
                inDoubleQuote = !inDoubleQuote;
                parmChars[j] = '\n';
                justSplitted = true;
                justQuoted = true;
            } else if(parmChars[i] == '\'' && !inDoubleQuote) {
                inSingleQuote = !inSingleQuote;
                parmChars[j] = '\n';
                justSplitted = true;
                justQuoted = true;
            } else if(!inSingleQuote && !inDoubleQuote && parmChars[i] == ' ') {
                parmChars[j] = '\n';
                justSplitted = true;
            }

            if(justSplitted && lastSplitted && (!lastQuoted || !justQuoted))
                j--;

            lastSplitted = justSplitted;
            justSplitted = false;

            lastQuoted = justQuoted;
            justQuoted = false;
        } else {
            escaped = false;
        }
    }

    if(lastQuoted)
        j--;

    return (new string(parmChars, 0, j)).Split(new[] { '\n' });
}

Based on Vapour in the Alley's answer, this one also supports ^ escapes.

Examples:

  • this is a test
    • this
    • is
    • a
    • test
  • this "is a" test
    • this
    • is a
    • test
  • this ^"is a^" test
    • this
    • "is
    • a"
    • test
  • this "" "is a ^^ test"
    • this
    • is a ^ test

It also supports multiple spaces (breaks arguments just one time per block of spaces).

Amelita answered 18/11, 2008 at 14:10 Comment(2)
The last of the three somehow interferes with Markdown and is not rendered as intended.Punishment
Fixed with a zero-width-space.Amelita
G
0

This is included in the System.CommandLine.Parsing package

No need to import the large code analysis package.

using System.CommandLine.Parsing;

var cliArgs = CommandLineStringSplitter.Instance.Split(s).ToArray();

https://learn.microsoft.com/en-us/dotnet/api/system.commandline.parsing.commandlinestringsplitter.split?view=system-commandline

Gooding answered 18/11, 2008 at 14:10 Comment(0)
R
0

I wrote a method to separate a file name from its arguments, for use with ProcessStartInfo which requires separating the file name and argument string.

For instance "C:\Users\Me\Something.exe" -a would give { "C:\Users\Me\Something.exe", "-a" } as a result

Code below:

    public static string[] SplitCommandFromArgs(string commandLine)
    {
        commandLine = commandLine.Trim();
        if (commandLine[0] == '"')
        {
            bool isEscaped = false;
            for (int c = 1; c < commandLine.Length; c++)
            {
                if (commandLine[c] == '"' && !isEscaped)
                {
                    return new string[] { commandLine.Substring(1, c - 1), commandLine.Substring(c + 1).Trim() };
                }
                isEscaped = commandLine[c] == '\\';
            }
        }
        else
        {
            for (int c = 1; c < commandLine.Length; c++) {
                if (commandLine[c] == ' ')
                {
                    return new string[] { commandLine.Substring(0, c), commandLine.Substring(c).Trim() };
                }
            }
        }
        return new string[] { commandLine, "" };
    }
Raft answered 18/11, 2008 at 14:10 Comment(0)
A
0

Here is the solution which treats space(s) (single or multiple spaces) as command line parameter separator and returns the real command line arguments:

static string[] ParseMultiSpacedArguments(string commandLine)
{
    var isLastCharSpace = false;
    char[] parmChars = commandLine.ToCharArray();
    bool inQuote = false;
    for (int index = 0; index < parmChars.Length; index++)
    {
        if (parmChars[index] == '"')
            inQuote = !inQuote;
        if (!inQuote && parmChars[index] == ' ' && !isLastCharSpace)
            parmChars[index] = '\n';

        isLastCharSpace = parmChars[index] == '\n' || parmChars[index] == ' ';
    }

    return (new string(parmChars)).Split('\n');
}
Andesine answered 18/11, 2008 at 14:10 Comment(0)
A
0

I have implemented state machine to have same parser results as if args would be passed into .NET application and processed in static void Main(string[] args) method.

    public static IList<string> ParseCommandLineArgsString(string commandLineArgsString)
    {
        List<string> args = new List<string>();

        commandLineArgsString = commandLineArgsString.Trim();
        if (commandLineArgsString.Length == 0)
            return args;

        int index = 0;
        while (index != commandLineArgsString.Length)
        {
            args.Add(ReadOneArgFromCommandLineArgsString(commandLineArgsString, ref index));
        }

        return args;
    }

    private static string ReadOneArgFromCommandLineArgsString(string line, ref int index)
    {
        if (index >= line.Length)
            return string.Empty;

        var sb = new StringBuilder(512);
        int state = 0;
        while (true)
        {
            char c = line[index];
            index++;
            switch (state)
            {
                case 0: //string outside quotation marks
                    if (c == '\\') //possible escaping character for quotation mark otherwise normal character
                    {
                        state = 1;
                    }
                    else if (c == '"') //opening quotation mark for string between quotation marks
                    {
                        state = 2;
                    }
                    else if (c == ' ') //closing arg
                    {
                        return sb.ToString();
                    }
                    else
                    {
                        sb.Append(c);
                    }

                    break;
                case 1: //possible escaping \ for quotation mark or normal character
                    if (c == '"') //If escaping quotation mark only quotation mark is added into result
                    {
                        state = 0;
                        sb.Append(c);
                    }
                    else // \ works as not-special character
                    {
                        state = 0;
                        sb.Append('\\');
                        index--;
                    }

                    break;
                case 2: //string between quotation marks
                    if (c == '"') //quotation mark in string between quotation marks can be escape mark for following quotation mark or can be ending quotation mark for string between quotation marks
                    {
                        state = 3;
                    }
                    else if (c == '\\') //escaping \ for possible following quotation mark otherwise normal character
                    {
                        state = 4;
                    }
                    else //text in quotation marks
                    {
                        sb.Append(c);
                    }

                    break;
                case 3: //quotation mark in string between quotation marks
                    if (c == '"') //Quotation mark after quotation mark - that means that this one is escaped and can added into result and we will stay in string between quotation marks state
                    {
                        state = 2;
                        sb.Append(c);
                    }
                    else //we had two consecutive quotation marks - this means empty string but the following chars (until space) will be part of same arg result as well
                    {
                        state = 0;
                        index--;
                    }

                    break;
                case 4: //possible escaping \ for quotation mark or normal character in string between quotation marks
                    if (c == '"') //If escaping quotation mark only quotation mark added into result
                    {
                        state = 2;
                        sb.Append(c);
                    }
                    else
                    {
                        state = 2;
                        sb.Append('\\');
                        index--;
                    }

                    break;
            }

            if (index == line.Length)
                return sb.ToString();
        }
    }
Alluvial answered 18/11, 2008 at 14:10 Comment(0)
O
0

Couldn't find anything I liked here. I hate to mess up the stack with yield magic for a small command-line (if it were a stream of a terabyte, it would be another story).

Here's my take, it supports quote escapes with double quotes like these:

param="a 15"" screen isn't bad" param2='a 15" screen isn''t bad' param3="" param4= /param5

result:

param="a 15" screen isn't bad"

param2='a 15" screen isn't bad'

param3=""

param4=

/param5

public static string[] SplitArguments(string commandLine)
{
    List<string> args         = new List<string>();
    List<char>   currentArg   = new List<char>();
    char?        quoteSection = null; // Keeps track of a quoted section (and the type of quote that was used to open it)
    char[]       quoteChars   = new[] {'\'', '\"'};
    char         previous     = ' '; // Used for escaping double quotes

    for (var index = 0; index < commandLine.Length; index++)
    {
        char c = commandLine[index];
        if (quoteChars.Contains(c))
        {
            if (previous == c) // Escape sequence detected
            {
                previous = ' '; // Prevent re-escaping
                if (!quoteSection.HasValue)
                {
                    quoteSection = c; // oops, we ended the quoted section prematurely
                    continue;         // don't add the 2nd quote (un-escape)
                }

                if (quoteSection.Value == c)
                    quoteSection = null; // appears to be an empty string (not an escape sequence)
            }
            else if (quoteSection.HasValue)
            {
                if (quoteSection == c)
                    quoteSection = null; // End quoted section
            }
            else
                quoteSection = c; // Start quoted section
        }
        else if (char.IsWhiteSpace(c))
        {
            if (!quoteSection.HasValue)
            {
                args.Add(new string(currentArg.ToArray()));
                currentArg.Clear();
                previous = c;
                continue;
            }
        }

        currentArg.Add(c);
        previous = c;
    }

    if (currentArg.Count > 0)
        args.Add(new string(currentArg.ToArray()));

    return args.ToArray();
}
Omidyar answered 18/11, 2008 at 14:10 Comment(0)
S
0

I don't think there are single quotes or ^ quotes for C# applications. The following function is working fine for me:

public static IEnumerable<String> SplitArguments(string commandLine)
{
    Char quoteChar = '"';
    Char escapeChar = '\\';
    Boolean insideQuote = false;
    Boolean insideEscape = false;

    StringBuilder currentArg = new StringBuilder();

    // needed to keep "" as argument but drop whitespaces between arguments
    Int32 currentArgCharCount = 0;                  

    for (Int32 i = 0; i < commandLine.Length; i++)
    {
        Char c = commandLine[i];
        if (c == quoteChar)
        {
            currentArgCharCount++;

            if (insideEscape)
            {
                currentArg.Append(c);       // found \" -> add " to arg
                insideEscape = false;
            }
            else if (insideQuote)
            {
                insideQuote = false;        // quote ended
            }
            else
            {
                insideQuote = true;         // quote started
            }
        }
        else if (c == escapeChar)
        {
            currentArgCharCount++;

            if (insideEscape)   // found \\ -> add \\ (only \" will be ")
                currentArg.Append(escapeChar + escapeChar);       

            insideEscape = !insideEscape;
        }
        else if (Char.IsWhiteSpace(c))
        {
            if (insideQuote)
            {
                currentArgCharCount++;
                currentArg.Append(c);       // append whitespace inside quote
            }
            else
            {
                if (currentArgCharCount > 0)
                    yield return currentArg.ToString();

                currentArgCharCount = 0;
                currentArg.Clear();
            }
        }
        else
        {
            currentArgCharCount++;
            if (insideEscape)
            {
                // found non-escaping backslash -> add \ (only \" will be ")
                currentArg.Append(escapeChar);                       
                currentArgCharCount = 0;
                insideEscape = false;
            }
            currentArg.Append(c);
        }
    }

    if (currentArgCharCount > 0)
        yield return currentArg.ToString();
}
Set answered 18/11, 2008 at 14:10 Comment(0)
G
0

Here's a one liner that gets the job done (see the one line that does all of the work inside the BurstCmdLineArgs(...) method).

Not what I'd call the most readable line of code, but you can break it out for readability's sake. It's simple on purpose and does not work well for all argument cases (like file name arguments that contain the split string character delimiter in them).

This solution has worked well in my solutions that use it. Like I said, it gets the job done without a rat's nest of code to handle every possible argument format n-factorial.

using System;
using System.Collections.Generic;
using System.Linq;

namespace CmdArgProcessor
{
    class Program
    {
        static void Main(string[] args)
        {
            // test switches and switches with values
            // -test1 1 -test2 2 -test3 -test4 -test5 5

            string dummyString = string.Empty;

            var argDict = BurstCmdLineArgs(args);

            Console.WriteLine("Value for switch = -test1: {0}", argDict["test1"]);
            Console.WriteLine("Value for switch = -test2: {0}", argDict["test2"]);
            Console.WriteLine("Switch -test3 is present? {0}", argDict.TryGetValue("test3", out dummyString));
            Console.WriteLine("Switch -test4 is present? {0}", argDict.TryGetValue("test4", out dummyString));
            Console.WriteLine("Value for switch = -test5: {0}", argDict["test5"]);

            // Console output:
            //
            // Value for switch = -test1: 1
            // Value for switch = -test2: 2
            // Switch -test3 is present? True
            // Switch -test4 is present? True
            // Value for switch = -test5: 5
        }

        public static Dictionary<string, string> BurstCmdLineArgs(string[] args)
        {
            var argDict = new Dictionary<string, string>();

            // Flatten the args in to a single string separated by a space.
            // Then split the args on the dash delimiter of a cmd line "switch".
            // E.g. -mySwitch myValue
            //  or -JustMySwitch (no value)
            //  where: all values must follow a switch.
            // Then loop through each string returned by the split operation.
            // If the string can be split again by a space character,
            // then the second string is a value to be paired with a switch,
            // otherwise, only the switch is added as a key with an empty string as the value.
            // Use dictionary indexer to retrieve values for cmd line switches.
            // Use Dictionary::ContainsKey(...) where only a switch is recorded as the key.
            string.Join(" ", args).Split('-').ToList().ForEach(s => argDict.Add(s.Split()[0], (s.Split().Count() > 1 ? s.Split()[1] : "")));

            return argDict;
        }
    }
}
Gib answered 18/11, 2008 at 14:10 Comment(0)
P
0

Try this code:

    string[] str_para_linha_comando(string str, out int argumentos)
    {
        string[] linhaComando = new string[32];
        bool entre_aspas = false;
        int posicao_ponteiro = 0;
        int argc = 0;
        int inicio = 0;
        int fim = 0;
        string sub;

        for(int i = 0; i < str.Length;)
        {
            if (entre_aspas)
            {
                // Está entre aspas
                sub = str.Substring(inicio+1, fim - (inicio+1));
                linhaComando[argc - 1] = sub;

                posicao_ponteiro += ((fim - posicao_ponteiro)+1);
                entre_aspas = false;
                i = posicao_ponteiro;
            }
            else
            {
            tratar_aspas:
                if (str.ElementAt(i) == '\"')
                {
                    inicio = i;
                    fim = str.IndexOf('\"', inicio + 1);
                    entre_aspas = true;
                    argc++;
                }
                else
                {
                    // Se não for aspas, então ler até achar o primeiro espaço em branco
                    if (str.ElementAt(i) == ' ')
                    {
                        if (str.ElementAt(i + 1) == '\"')
                        {
                            i++;
                            goto tratar_aspas;
                        }

                        // Pular os espaços em branco adiconais
                        while(str.ElementAt(i) == ' ') i++;

                        argc++;
                        inicio = i;
                        fim = str.IndexOf(' ', inicio);
                        if (fim == -1) fim = str.Length;
                        sub = str.Substring(inicio, fim - inicio);
                        linhaComando[argc - 1] = sub;
                        posicao_ponteiro += (fim - posicao_ponteiro);

                        i = posicao_ponteiro;
                        if (posicao_ponteiro == str.Length) break;
                    }
                    else
                    {
                        argc++;
                        inicio = i;
                        fim = str.IndexOf(' ', inicio);
                        if (fim == -1) fim = str.Length;

                        sub = str.Substring(inicio, fim - inicio);
                        linhaComando[argc - 1] = sub;
                        posicao_ponteiro += fim - posicao_ponteiro;
                        i = posicao_ponteiro;
                        if (posicao_ponteiro == str.Length) break;
                    }
                }
            }
        }

        argumentos = argc;

        return linhaComando;
    }

It's written in Portuguese.

Privation answered 18/11, 2008 at 14:10 Comment(2)
rather documentation is portugueseSpotter
@EnamulHassan I would say the code is also in Portuguese, e.g. posicao_ponteiro += ((fim - posicao_ponteiro)+1);.Coffer
C
0

You can have a look at the code I've posted yesterday:

[C#] Path & arguments strings

It splits a filename + arguments into string[]. Short paths, environment variables, and missing file extensions are handled.

(Initially it was for UninstallString in Registry.)

Calends answered 18/11, 2008 at 14:10 Comment(0)
F
0

This is a reply to Anton's code, which do not work with escaped quotes. I modified 3 places.

  1. The constructor for StringBuilder in SplitCommandLineArguments, replacing any \" with \r
  2. In the for-loop in SplitCommandLineArguments, I now replace the \r character back to \".
  3. Changed the SplitCommandLineArgument method from private to public static.

public static string[] SplitCommandLineArgument( String argumentString )
{
    StringBuilder translatedArguments = new StringBuilder( argumentString ).Replace( "\\\"", "\r" );
    bool InsideQuote = false;
    for ( int i = 0; i < translatedArguments.Length; i++ )
    {
        if ( translatedArguments[i] == '"' )
        {
            InsideQuote = !InsideQuote;
        }
        if ( translatedArguments[i] == ' ' && !InsideQuote )
        {
            translatedArguments[i] = '\n';
        }
    }

    string[] toReturn = translatedArguments.ToString().Split( new char[] { '\n' }, StringSplitOptions.RemoveEmptyEntries );
    for ( int i = 0; i < toReturn.Length; i++ )
    {
        toReturn[i] = RemoveMatchingQuotes( toReturn[i] );
        toReturn[i] = toReturn[i].Replace( "\r", "\"" );
    }
    return toReturn;
}

public static string RemoveMatchingQuotes( string stringToTrim )
{
    int firstQuoteIndex = stringToTrim.IndexOf( '"' );
    int lastQuoteIndex = stringToTrim.LastIndexOf( '"' );
    while ( firstQuoteIndex != lastQuoteIndex )
    {
        stringToTrim = stringToTrim.Remove( firstQuoteIndex, 1 );
        stringToTrim = stringToTrim.Remove( lastQuoteIndex - 1, 1 ); //-1 because we've shifted the indicies left by one
        firstQuoteIndex = stringToTrim.IndexOf( '"' );
        lastQuoteIndex = stringToTrim.LastIndexOf( '"' );
    }
    return stringToTrim;
}
Fraga answered 18/11, 2008 at 14:10 Comment(1)
I'm tackling this same issue, you would have thought that in this day and age a simple solution would exist for unit testing command line argument strings. All I want to be sure of is the behavior that will result from a given commandline argument string. I'm giving up for now and will create unit tests for string[] but may add some integration tests to cover this off.Politburo
A
0

Currently, this is the code that I have:

    private String[] SplitCommandLineArgument(String argumentString)
    {
        StringBuilder translatedArguments = new StringBuilder(argumentString);
        bool escaped = false;
        for (int i = 0; i < translatedArguments.Length; i++)
        {
            if (translatedArguments[i] == '"')
            {
                escaped = !escaped;
            }
            if (translatedArguments[i] == ' ' && !escaped)
            {
                translatedArguments[i] = '\n';
            }
        }

        string[] toReturn = translatedArguments.ToString().Split(new char[] { '\n' }, StringSplitOptions.RemoveEmptyEntries);
        for(int i = 0; i < toReturn.Length; i++)
        {
            toReturn[i] = RemoveMatchingQuotes(toReturn[i]);
        }
        return toReturn;
    }

    public static string RemoveMatchingQuotes(string stringToTrim)
    {
        int firstQuoteIndex = stringToTrim.IndexOf('"');
        int lastQuoteIndex = stringToTrim.LastIndexOf('"');
        while (firstQuoteIndex != lastQuoteIndex)
        {
            stringToTrim = stringToTrim.Remove(firstQuoteIndex, 1);
            stringToTrim = stringToTrim.Remove(lastQuoteIndex - 1, 1); //-1 because we've shifted the indicies left by one
            firstQuoteIndex = stringToTrim.IndexOf('"');
            lastQuoteIndex = stringToTrim.LastIndexOf('"');
        }
        return stringToTrim;
    }

It doesn't work with escaped quotes, but it works for the cases that I've come up against so far.

Apples answered 18/11, 2008 at 14:10 Comment(0)
O
-2

I am not sure if I understood you, but is the problem that the character used as splitter, is also to be found inside the text? (Except for that it is escaped with double "?)

If so, I would create a for loop, and replace all instances where <"> is present with <|> (or another "safe" character, but make sure that it only replaces <">, and not <"">

After iterating the string, I would do as previously posted, split the string, but now on the character <|>.

Oppidan answered 18/11, 2008 at 14:10 Comment(2)
The double ""'s are beceause its a @".." string literal, The double "'s inside the @".." string are equivalent to a \ escaped " in a normal stringApples
"the only restriction (I beleive) is that the strings are space-delimited, unless the space uccurs within a "..." block" -> Might be shooting a bird with a bazooka, but put a boolean which goes "true" when inside a quote, and if a space is detected inside while "true", continue, else < > = <|>Oppidan
S
-5

Yes, the string object has a built in function called Split() that takes a single parameter specifying the character to look for as a delimiter, and returns an array of strings (string[]) with the individual values in it.

Schiller answered 18/11, 2008 at 14:10 Comment(2)
This would split the src:"C:\tmp\Some Folder\Sub Folder" portion incorrectly.Apples
What about quotes inside the string that temporarily switch off splitting on spaces?Hogfish

© 2022 - 2024 — McMap. All rights reserved.