Frage Geteilte Zeichenfolge mit Befehlszeilenparametern in Zeichenfolge [] in C #


Ich habe eine einzelne Zeichenfolge, die die Befehlszeilenparameter enthält, die an eine andere ausführbare Datei übergeben werden, und ich muss die Zeichenfolge [], die die einzelnen Parameter enthält, auf die gleiche Weise wie C # extrahieren, wenn die Befehle in der Befehlszeile angegeben wurden. Die Zeichenfolge [] wird verwendet, wenn ein anderer Assembly-Einstiegspunkt über Reflektion ausgeführt wird.

Gibt es dafür eine Standardfunktion? Oder gibt es eine bevorzugte Methode (Regex?), Um die Parameter korrekt zu teilen? Es muss mit '' 'getrennten Strings umgehen, die Leerzeichen korrekt enthalten können, sodass ich nicht einfach auf' 'splitten kann.

Beispielzeichenfolge:

string parameterString = @"/src:""C:\tmp\Some Folder\Sub Folder"" /users:""abcdefg@hijkl.com"" tasks:""SomeTask,Some Other Task"" -someParam foo";

Beispielergebnis:

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

Ich brauche keine Befehlszeilen-Parsing-Bibliothek, nur um die Zeichenfolge [] zu erhalten, die generiert werden soll.

Aktualisieren: Ich musste das erwartete Ergebnis so ändern, dass es mit dem übereinstimmt, was tatsächlich von C # generiert wurde (die Extra 's in den Split-Strings wurden entfernt)


75


Ursprung


Antworten:


In Ergänzung zu Gute und rein verwaltete Lösung durch EarwickerEs kann der Vollständigkeit halber erwähnt werden, dass Windows auch die CommandLineToArgvW Funktion zum Zerlegen eines Strings in ein Array von Strings:

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

Analysiert eine Unicode-Befehlszeilenfolge   und gibt ein Array von Zeigern an   die Befehlszeilenargumente zusammen mit   eine Anzahl solcher Argumente in gewisser Weise   das ist ähnlich dem Standard C   Laufzeit-Argv- und Argc-Werte.

Ein Beispiel für das Aufrufen dieser API aus C # und das Entpacken des resultierenden Zeichenfolgenarrays in verwaltetem Code finden Sie unter "Konvertieren der Befehlszeilenfolge in Args [] mithilfe der CommandLineToArgvW () - API. "Unten ist eine etwas einfachere Version des gleichen Codes:

[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);
    }
}

60



Es ärgert mich, dass es keine Funktion gibt, eine Zeichenfolge basierend auf einer Funktion aufzuteilen, die jedes Zeichen untersucht. Wenn es so wäre, könnte man es so schreiben:

    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));
    }

Obwohl Sie das geschrieben haben, warum schreiben Sie nicht die notwendigen Erweiterungsmethoden. Okay, du hast mich dazu überredet ...

Erstens, meine eigene Version von Split, die eine Funktion übernimmt, die entscheiden muss, ob das angegebene Zeichen die Zeichenkette teilen soll:

    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);
    }

Je nach Situation kann es zu leeren Strings kommen, aber diese Informationen sind möglicherweise in anderen Fällen nützlich, daher entferne ich die leeren Einträge in dieser Funktion nicht.

Zweitens (und noch weltlicher) ein kleiner Helfer, der ein passendes Anführungszeichen vom Anfang und Ende eines Strings abschneidet. Es ist wählerischer als die Standard-Trim-Methode - es wird nur ein Zeichen von jedem Ende abgeschnitten, und es wird nicht von nur einem Ende abgeschnitten:

    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;
    }

Und ich nehme an, dass Sie auch einige Tests wollen. Gut, dann gut. Aber das muss absolut das letzte sein! Zuerst eine Hilfsfunktion, die das Ergebnis der Teilung mit dem erwarteten Array-Inhalt vergleicht:

    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]);
    }

Dann kann ich Tests wie folgt schreiben:

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

Hier ist der Test für Ihre Anforderungen:

        Test(@"/src:""C:\tmp\Some Folder\Sub Folder"" /users:""abcdefg@hijkl.com"" tasks:""SomeTask,Some Other Task"" -someParam",
             @"/src:""C:\tmp\Some Folder\Sub Folder""", @"/users:""abcdefg@hijkl.com""", @"tasks:""SomeTask,Some Other Task""", @"-someParam");

Beachten Sie, dass die Implementierung das zusätzliche Feature hat, dass sie Anführungszeichen um ein Argument entfernt, wenn dies sinnvoll ist (dank der TrimMatchingQuotes-Funktion). Ich glaube, das ist Teil der normalen Befehlszeileninterpretation.


87



Der Windows-Befehlszeilenparser verhält sich genau so, wie Sie es nennen, aufgeteilt in Leerzeichen, es sei denn, es gibt ein nicht geschlossenes Zitat davor. Ich würde empfehlen, den Parser selbst zu schreiben. So etwas mag vielleicht sein:

    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');
    }

21



Ich nahm die Antwort von Jeffrey L Whitledge und verbesserte es ein wenig. Ich habe noch nicht genug Credits, um seine Antwort zu kommentieren.

Es unterstützt jetzt sowohl einfache als auch doppelte Anführungszeichen. Sie können Anführungszeichen in den Parametern selbst verwenden, indem Sie andere typisierte Anführungszeichen verwenden.

Es entfernt auch die Anführungszeichen von den Argumenten, da diese nicht zu den Argumentinformationen beitragen.

    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);
    }

9



Environment.GetCommandLineArgs ()


5



Google sagt: C # /. NET Befehlszeilenargumente Parser


4



Das Gute und rein verwaltete Lösung durch Earwicker Fehler bei der Behandlung von Argumenten wie dieser:

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

Es gab 3 Elemente zurück:

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

Also hier ist ein Fix zur Unterstützung des "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));
}

Getestet mit 2 zusätzlichen Fällen:

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

Auch darauf hingewiesen, dass die akzeptierte Antwort durch Atif Aziz was benutzt BefehlsLineToArgvW auch gescheitert. Es gab 4 Elemente zurück:

He whispered to her \ 
I 
love 
you". 

Ich hoffe, dies hilft jemandem, der in Zukunft nach einer solchen Lösung sucht.


4