Pertanyaan Split string yang berisi parameter baris perintah ke string [] di C #


Saya memiliki satu string yang berisi parameter baris perintah yang akan diteruskan ke eksekusi lain dan saya perlu mengekstraksi string [] yang berisi parameter individual dengan cara yang sama seperti C # jika perintah telah ditentukan pada baris perintah. String [] akan digunakan ketika mengeksekusi entry-point rakitan yang lain melalui refleksi.

Apakah ada fungsi standar untuk ini? Atau apakah ada metode yang disukai (regex?) Untuk membagi parameter dengan benar? Ini harus menangani string yang dibatasi '' 'yang mungkin berisi spasi dengan benar, jadi saya tidak bisa membaginya begitu saja' '.

String contoh:

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

Contoh hasil:

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

Saya tidak memerlukan parsing parsing command-line, hanya cara untuk mendapatkan String [] yang harus dihasilkan.

Memperbarui: Saya harus mengubah hasil yang diharapkan untuk mencocokkan apa yang sebenarnya dihasilkan oleh C # (dihapus tambahan "'s dalam string split)


75


asal


Jawaban:


Selain itu solusi yang dikelola dengan baik dan murni oleh Earwicker, mungkin perlu disebutkan, demi kelengkapan, bahwa Windows juga menyediakan CommandLineToArgvW berfungsi untuk putus string ke dalam array string:

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

Mem-parsing string baris perintah Unicode   dan mengembalikan array pointer ke   argumen baris perintah, bersama dengan   hitungan argumen semacam itu, dengan cara   yang mirip dengan standar C   nilai argv dan argc run-time.

Contoh memanggil API ini dari C # dan membongkar string array yang dihasilkan dalam kode yang dikelola dapat ditemukan di, “Mengubah Command Line String ke Args [] menggunakan CommandLineToArgvW () API. ”Di bawah ini adalah versi yang lebih sederhana dari kode yang sama:

[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



Ini mengganggu saya bahwa tidak ada fungsi untuk membagi string berdasarkan fungsi yang memeriksa setiap karakter. Jika ada, Anda bisa menulisnya seperti ini:

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

Meskipun telah menulis itu, mengapa tidak menulis metode ekstensi yang diperlukan. Oke, Anda berbicara saya ke dalamnya ...

Pertama, versi saya sendiri dari Split yang mengambil fungsi yang harus memutuskan apakah karakter yang ditentukan harus membagi 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);
    }

Mungkin menghasilkan beberapa string kosong tergantung pada situasi, tapi mungkin informasi itu akan berguna dalam kasus lain, jadi saya tidak menghapus entri kosong dalam fungsi ini.

Kedua (dan lebih sederhana) pembantu kecil yang akan memangkas sepasang kutip yang cocok dari awal dan akhir string. Ini lebih rewel daripada metode Trim standar - itu hanya akan memangkas satu karakter dari setiap ujungnya, dan itu tidak akan langsing hanya dari satu ujung:

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

Dan saya kira Anda juga menginginkan beberapa tes. Baiklah, baiklah kalau begitu. Tapi ini pasti benar-benar hal terakhir! Pertama fungsi pembantu yang membandingkan hasil perpecahan dengan konten larik yang diharapkan:

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

Lalu saya bisa menulis tes seperti ini:

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

Berikut tes untuk kebutuhan Anda:

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

Perhatikan bahwa implementasi memiliki fitur tambahan yang akan menghapus tanda kutip di sekitar argumen jika itu masuk akal (terima kasih kepada fungsi TrimMatchingQuotes). Saya percaya itu bagian dari interpretasi baris perintah normal.


87



Pengurai baris perintah Windows berperilaku persis seperti yang Anda katakan, terbagi pada ruang kecuali ada kutipan yang tidak ditutup sebelumnya. Saya akan merekomendasikan menulis parser sendiri. Sesuatu seperti ini mungkin:

    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



Saya mengambil jawaban dari Jeffrey L Whitledge dan meningkatkannya sedikit. Saya belum punya cukup kredit untuk mengomentari jawabannya.

Sekarang mendukung tanda kutip tunggal dan ganda. Anda dapat menggunakan tanda kutip dalam parameter itu sendiri dengan menggunakan tanda kutip lainnya.

Ini juga strip kutipan dari argumen karena ini tidak berkontribusi pada informasi argumen.

    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 mengatakan: C # /. NET Command Line Arguments Parser


4



Itu solusi yang dikelola dengan baik dan murni oleh Earwicker gagal menangani argumen seperti ini:

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

Itu mengembalikan 3 elemen:

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

Jadi di sini adalah perbaikan untuk mendukung "kutipan \" melarikan diri \ "kutipan":

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

Diuji dengan 2 tambahan kasus:

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

Juga mencatat bahwa jawaban yang diterima oleh Atif Aziz yang menggunakan CommandLineToArgvW juga gagal. Itu mengembalikan 4 elemen:

He whispered to her \ 
I 
love 
you". 

Semoga ini membantu seseorang yang mencari solusi seperti itu di masa depan.


4



Saya suka iterators, dan saat ini Linq membuat IEnumerable dengan mudah digunakan sebagai array string, jadi saya mengikuti semangat Jeffrey L Whitledge jawabannya adalah (sebagai metode ekstensi untuk 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();
    }

3



Ini artikel proyek kode adalah apa yang saya gunakan di masa lalu, itu kode yang bagus, tetapi mungkin berhasil.

Ini artikel msdn adalah satu-satunya hal yang bisa saya temukan yang menjelaskan bagaimana C # mem-parsing argumen baris perintah.

Semoga itu membantu!


2