web-dev-qa-db-fra.com

Fractionner la chaîne contenant les paramètres de ligne de commande dans la chaîne [] en C #

J'ai une seule chaîne qui contient les paramètres de ligne de commande à transmettre à un autre exécutable et j'ai besoin d'extraire la chaîne [] contenant les paramètres individuels de la même manière que C # si les commandes avaient été spécifiées sur la ligne de commande. La chaîne [] sera utilisée lors de l'exécution d'un autre point d'entrée d'assemblages via réflexion.

Existe-t-il une fonction standard pour cela? Ou existe-t-il une méthode préférée (regex?) Pour fractionner les paramètres correctement? Il doit gérer des "" "chaînes délimitées pouvant contenir des espaces correctement, je ne peux donc pas me séparer".

Exemple de chaîne:

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

Exemple de résultat:

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

Je n'ai pas besoin d'une bibliothèque d'analyse en ligne de commande, mais simplement d'un moyen d'obtenir la chaîne [] à générer.

Mise à jour : Je devais modifier le résultat attendu afin qu'il corresponde à ce qui est réellement généré par C # (supprimait les "" supplémentaires dans les chaînes scindées).

79
Anton

En plus de la solution gérée pure et pure de Earwicker , il peut être intéressant de mentionner que, par souci d'exhaustivité, Windows fournit également la fonction CommandLineToArgvW pour interrompre une chaîne dans un tableau de chaînes:

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

Analyse une chaîne de ligne de commande Unicode et renvoie un tableau de pointeurs sur les arguments en ligne de commande, avec un décompte de tels arguments, en quelque sorte cela ressemble à la norme C valeurs d'exécution argv et argc au moment de l'exécution.

Vous trouverez un exemple d'appel de cette API à partir de C # et de décompression du tableau de chaînes résultant en code managé à l'adresse suivante: « Conversion d'une chaîne de ligne de commande en args [] à l'aide de l'API CommandLineToArgvW () ». En voici une version légèrement plus simple. du même 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);
    }
}
64
Atif Aziz

Cela m'ennuie qu'il n'y ait pas de fonction pour diviser une chaîne en fonction d'une fonction qui examine chaque caractère. Si c'était le cas, vous pourriez l'écrire comme ceci:

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

Bien qu'ayant écrit cela, pourquoi ne pas écrire les méthodes d'extension nécessaires. D'accord, vous m'avez parlé dedans ...

Tout d'abord, ma propre version de Split qui prend une fonction qui doit décider si le caractère spécifié doit fractionner la chaîne:

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

Cela peut donner des chaînes vides en fonction de la situation, mais peut-être que cette information sera utile dans d'autres cas, aussi je ne supprime pas les entrées vides de cette fonction.

Deuxièmement (et plus banalement) un petit assistant qui va couper une paire de citations correspondante au début et à la fin d'une chaîne. C'est plus difficile que la méthode de rognage standard - cela ne coupera qu'un caractère de chaque extrémité, et cela ne coupera pas d'un seul bout:

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

Et je suppose que vous voudrez également des tests. Bon, d'accord alors. Mais cela doit être absolument la dernière chose! Tout d'abord, une fonction d'assistance qui compare le résultat de la scission au contenu attendu du tableau:

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

Ensuite, je peux écrire des tests comme celui-ci:

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

Voici le test pour vos besoins:

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

Notez que l'implémentation a la fonctionnalité supplémentaire de supprimer les guillemets autour d'un argument si cela a du sens (grâce à la fonction TrimMatchingQuotes). Je crois que cela fait partie de l'interprétation normale en ligne de commande.

93
Daniel Earwicker

L'analyseur en ligne de commande Windows se comporte exactement comme vous le dites, séparé de l'espace sauf si une citation non fermée la précédait. Je recommanderais d'écrire l'analyseur vous-même. Quelque chose comme ça peut-être:

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

J'ai pris la réponse de Jeffrey L Whitledge et l'ai améliorée un peu.

Il prend désormais en charge les guillemets simples et doubles. Vous pouvez utiliser des guillemets dans les paramètres en utilisant d'autres guillemets dactylographiés.

Il supprime également les guillemets des arguments car ceux-ci ne contribuent pas aux informations sur les arguments.

    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);
    }
12
Vapour in the Alley

La bonne solution pure et gérée par Earwicker n'a pas réussi à gérer des arguments tels que:

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

Il a renvoyé 3 éléments:

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

Voici donc un correctif pour prendre en charge la "citation" "d'échappement":

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

Testé avec 2 cas supplémentaires:

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

A également noté que le accepté réponse par Atif Aziz qui utilise CommandLineToArgvW a également échoué. Il a renvoyé 4 éléments:

He whispered to her \ 
I 
love 
you". 

J'espère que cela aidera quelqu'un qui recherche une telle solution dans le futur.

6
Kevin Thach

J'aime les itérateurs, et de nos jours LINQ rend IEnumerable<String> aussi facilement utilisable que des tableaux de chaînes, donc ma prise suit l'esprit de la réponse de Jeffrey L Whitledge est ( comme méthode d'extension à 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();
}
4
Monoman
4
Mark Cidade

Ceci L'article de Code Project est ce que j'ai utilisé par le passé. C'est un bon morceau de code, mais cela pourrait fonctionner.

Cet article MSDN est la seule chose que je pourrais trouver qui explique comment C # analyse les arguments de ligne de commande.

2
Zachary Yates

Dans votre question, vous avez demandé une expression rationnelle, et je suis un grand fan et utilisateur d’elles. Ainsi, lorsque j’ai eu besoin de faire le même argument en deux étapes, j’écrivais ma propre expression rationnelle après avoir cherché sur Google sans trouver de solution simple. J'aime les solutions courtes, alors j'en ai fait une et la voici:

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

Il gère les espaces et les guillemets entre guillemets et convertit les "" en "inclus. N'hésitez pas à utiliser le code!

1
Thomas Petersson

Utilisation:

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

D'après la réponse de Vapor in the Alley , celle-ci prend également en charge ^ échappées.

Exemples:

  • c'est un test
    • ce
    • est
    • une
    • tester
  • c'est un test
    • ce
    • est un
    • tester
  • ceci ^ "est un ^" test
    • ce
    • "est
    • une"
    • tester
  • ce "" "est un test ^^"
    • ce
    • Le plus grand centre commercial 
    • est un ^ test

Il prend également en charge plusieurs espaces (casse les arguments une seule fois par bloc d'espaces).

1
Fabio Iotti

Une solution purement gérée pourrait être utile. Il y a trop de commentaires "problème" pour la fonction WINAPI et celle-ci n'est pas disponible sur d'autres plates-formes. Voici mon code qui a un comportement bien défini (que vous pouvez changer si vous le souhaitez).

Il devrait faire la même chose que ce que font .NET/Windows en fournissant ce paramètre string[] args, et je l’ai comparé à un certain nombre de valeurs "intéressantes".

Il s'agit d'une implémentation classique de machine à états qui extrait chaque caractère de la chaîne d'entrée et l'interprète pour l'état actuel, produisant une sortie et un nouvel état. L'état est défini dans les variables escape, inQuote, hadQuote et prevCh et la sortie est collectée dans currentArg et args.

Certaines des spécialités que j'ai découvertes par des expériences sur une commande réelle Invite (Windows 7): \\ produit \, \" produit ", "" dans une plage citée génère ".

Le caractère ^ semble également magique: il disparaît toujours lorsque vous ne le doublez pas. Sinon, cela n’a aucun effet sur une vraie ligne de commande. Mon implémentation ne supporte pas cela, car je n’ai trouvé aucun motif dans ce comportement. Peut-être que quelqu'un en sait plus à ce sujet.

Quelque chose qui ne rentre pas dans ce modèle est la commande suivante:

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

La commande cmd semble intercepter les guillemets extérieurs et prendre le reste mot pour mot. Il doit y avoir une sauce magique spéciale dans cela.

Je n'ai pas fait de repères sur ma méthode, mais considérez-le assez rapidement. Il n'utilise pas Regex et ne fait aucune concaténation de chaîne, mais utilise plutôt un StringBuilder pour collecter les caractères d'un argument et les mettre dans une liste.

/// <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();
}
1
ygoe

Actuellement, c'est le code que j'ai:

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

Cela ne fonctionne pas avec des citations échappées, mais cela fonctionne pour les cas que j'ai rencontrés jusqu'à présent.

0
Anton

Oh diable. C'est tout ... Eugh. Mais c'est un fonctionnaire légitime. De Microsoft en C # pour .NET Core, peut-être uniquement Windows, peut-être multi-plateforme, mais sous licence MIT.

Sélectionnez des friandises, des déclarations de méthodes et des commentaires notables;

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

C’est du code transféré de .NET Framework à .NET Core à partir de ce que je suppose soit la bibliothèque C de MSVC, soit CommandLineToArgvW.

Voici ma tentative timide de gérer certaines manigances avec des expressions régulières et d'ignorer l'argument bit zéro. C'est un peu sorcier.

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

Testé un peu juste sur la sortie générée wacky. Sa sortie correspond à un pourcentage non négligeable de ce que les singes ont tapé et parcouru CommandLineToArgvW.

0
TylerY86

Voici une ligne qui effectue le travail (voir la ligne qui effectue tout le travail dans la méthode BurstCmdLineArgs (...)).

Ce n'est pas ce que j'appellerais la ligne de code la plus lisible, mais vous pouvez l'éclairer par souci de lisibilité. C'est simple et intentionnel et ne fonctionne pas bien pour tous les arguments (comme les arguments de nom de fichier qui contiennent le délimiteur de caractères de chaîne fractionnée).

Cette solution a bien fonctionné dans mes solutions qui l'utilisent. Comme je l'ai dit, le travail est effectué sans code de rat pour gérer tous les formats d'argument n-factoriels possibles.

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;
        }
    }
}
0
Vance McCorkle

Vous pouvez consulter le code que j'ai posté hier:

[C #] Chemin et arguments chaines

Il divise un nom de fichier + arguments en chaîne []. Les chemins courts, les variables d'environnement et les extensions de fichier manquantes sont gérés.

(Initialement, c'était pour UninstallString dans le registre.)

0

Je n'ai rien trouvé que j'ai aimé ici. Je déteste gâcher la pile avec la magie du rendement pour une petite ligne de commande (s'il s'agissait d'un flux de téraoctets, ce serait une autre histoire).

Voici ma prise en charge, il prend en charge les échappements de guillemets avec des guillemets doubles comme ceux-ci:

param = "un 15" "écran n'est pas mauvais" param2 = 'un 15 "écran n'est pas mauvais' param3 =" "param4 =/param5

résultat:

param = "un écran de 15" n'est pas mauvais "

param2 = 'un écran de 15 "n'est pas mauvais'

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();
}
0
Louis Somers

Ceci est une réponse au code d'Anton, qui ne fonctionne pas avec les citations échappées. J'ai modifié 3 places.

  1. Le constructeur pour StringBuilder dans SplitCommandLineArguments, en remplaçant tout \ " par \ r
  2. Dans le for-loop in SplitCommandLineArguments, je remplace maintenant le caractère \ r par \ ".
  3. Modification de la méthode SplitCommandLineArgument de privé à public statique.

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;
}
0
CS.

Je ne pense pas qu'il existe des guillemets simples ou ^ guillemets pour les applications C # . La fonction suivante fonctionne correctement pour moi:

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();
}
0
HarryP

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

C'est écrit en portugais.

0
Lucas De Jesus