104 votes

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

J'ai une seule chaîne de caractères qui contient les paramètres de ligne de commande pour être transmis à une autre exécutable et j'ai besoin d'extraire le string[] contenant les paramètres individuels de la même manière que le C# serait le cas si les commandes ont été spécifiés sur la ligne de commande. Le string[] sera utilisé lors de l'exécution d'un autre des assemblées du point d'entrée de la via de la réflexion.

Est-il une fonction standard pour cela? Ou est-il une méthode préférée (regex?) pour le fractionnement correctement les paramètres? Il doit gérer '"' délimité par des chaînes de caractères qui peut contenir des espaces correctement, donc je ne peux pas juste diviser le'.

Exemple de chaîne de caractères:

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

Exemple de résultat:

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

Je n'ai pas besoin de ligne de commande, l'analyse de la bibliothèque, juste un moyen d'obtenir le String[] qui devraient être générés.

Mise à jour: j'ai dû changer le résultat attendu pour correspondre à ce qui est réellement produite par C# (a enlevé le "'s de la scission de chaînes de caractères)

112voto

Daniel Earwicker Points 63298

Ça m'énerve qu'il n'y a pas de fonction pour diviser une chaîne basée sur une fonction qui examine chaque personnage. S'il y avait, vous pouvez 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. Bon, vous avez parlé de moi...

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

Il peut produire certains des chaînes vides en fonction de la situation, mais peut-être que l'information sera utile dans d'autres cas, donc je n'ai pas supprimer les entrées vides dans cette fonction.

Deuxièmement (et plus prosaïquement) une petite aide qui sera couper une paire de guillemets de début et de fin de chaîne. C'est plus pointilleux que la version standard et la méthode - il seulement de la garniture d'un caractère à partir de chaque extrémité, et il ne sera pas de la garniture à partir d'une seule fin:

    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 aurez envie de certains tests. Bien, très bien. Mais ce doit être absolument la dernière chose! D'abord une fonction d'assistance qui compare le résultat de la scission avec le contenu 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]);
    }

Alors je peux écrire des tests de ce genre:

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

Voici le test de vos exigences:

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

Notez que la mise en œuvre a la fonction supplémentaire que cela va enlever les guillemets autour d'un argument si cela a du sens (grâce à la TrimMatchingQuotes fonction). Je crois que c'est une partie de la commande normale de ligne de l'interprétation.

84voto

Atif Aziz Points 16967

En plus de la bonne et pure solution gérée par Earwicker, il peut être utile de mentionner, par souci d'exhaustivité, que Windows fournit également l' CommandLineToArgvW fonction pour rompre une chaîne dans un tableau de chaînes de caractères:

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

Analyse Unicode chaîne de ligne de commande et retourne un tableau de pointeurs les arguments de ligne de commande, avec un nombre de ces arguments, de façon à qui est similaire à la norme C au moment de l'exécution argv et argc valeurs.

Un exemple de l'appel de cette API à partir de C# et le déballage de la chaîne résultante tableau en code managé peut être trouvé à la, "la Conversion de la Ligne de Commande String Args[] à l'aide de CommandLineToArgvW() de l'API." Ci-dessous est un peu plus simple version 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);
    }
}

27voto

Jeffrey L Whitledge Points 27574

L'analyseur en ligne de commande Windows se comporte comme vous le dites, séparé de l'espace à moins qu'un guillemet non fermé ne le précède. 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');
    }
 

4voto

Mark Cidade Points 53945

3voto

spoulson Points 13391

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X