Pisahkan string yang berisi parameter baris perintah menjadi string [] di C #

91

Saya memiliki satu string yang berisi parameter baris perintah untuk diteruskan ke lain yang dapat dieksekusi dan saya perlu mengekstrak string [] yang berisi parameter individu dengan cara yang sama seperti C # jika perintah telah ditentukan pada baris perintah. String [] akan digunakan saat menjalankan entry-point rakitan lain melalui refleksi.

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

Contoh string:

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

Contoh hasil:

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

Saya tidak membutuhkan pustaka parsing baris perintah, hanya cara untuk mendapatkan String [] yang harus dibuat.

Pembaruan : Saya harus mengubah hasil yang diharapkan agar sesuai dengan apa yang sebenarnya dihasilkan oleh C # (menghapus ekstra "di string terpisah)

Anton
sumber
Google mengatakan: C # / .NET Command Line Arguments Parser
spoulson
5
Setiap kali seseorang menanggapi, Anda sepertinya memiliki keberatan berdasarkan materi yang tidak ada di postingan Anda. Saya menyarankan agar Anda memperbarui posting Anda dengan materi ini. Anda mungkin mendapatkan jawaban yang lebih baik.
tvanfosson
1
Pertanyaan bagus, mencari hal yang sama. Berharap untuk menemukan seseorang berkata "hey .net mengekspos itu di sini ..." :) Jika saya menemukan itu pada suatu saat, saya akan mempostingnya di sini, meskipun ini seperti berusia 6 tahun. Masih pertanyaan yang valid!
MikeJansen
Saya telah membuat versi yang dikelola murni dalam jawaban di bawah karena saya membutuhkan fungsi ini juga.
ygoe

Jawaban:

75

Selain solusi terkelola yang baik dan murni oleh Earwicker , mungkin perlu disebutkan, demi kelengkapan, bahwa Windows juga menyediakan CommandLineToArgvWfungsi untuk memecah string menjadi array string:

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

Mengurai string baris perintah Unicode dan mengembalikan larik pointer ke argumen baris perintah, bersama dengan jumlah argumen tersebut, dengan cara yang mirip dengan nilai argv dan argc waktu proses C standar.

Contoh pemanggilan API ini dari C # dan membongkar larik string yang dihasilkan dalam kode terkelola dapat ditemukan di, “ Mengonversi String Baris Perintah ke Arg [] menggunakan CommandLineToArgvW () API .” Di bawah ini adalah versi yang sedikit 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);
    }
}
Atif Aziz
sumber
1
Fungsi ini mengharuskan Anda keluar dari garis miring terbalik dari jalur di dalam tanda kutip. "C: \ Program Files \" harus "C: \ Program Files \\" agar ini berfungsi untuk mengurai string dengan benar.
Magnus Lindhe
8
Perlu juga dicatat bahwa CommandLineArgvW mengharapkan argumen pertama adalah nama program, dan sihir penguraian yang diterapkan tidak sama jika tidak diteruskan. Anda dapat memalsukannya dengan sesuatu seperti:CommandLineToArgs("foo.exe " + commandLine).Skip(1).ToArray();
Scott Wegner
4
Demi kelengkapan, MSVCRT tidak menggunakan CommandLineToArgvW () untuk mengonversi baris perintah menjadi argc / argv. Ia menggunakan kodenya sendiri, yang berbeda. Misalnya, coba panggil CreateProcess dengan string ini: a "b c" def. Di main () Anda akan mendapatkan 3 argumen (seperti yang didokumentasikan di MSDN), tetapi kombinasi CommandLineToArgvW () / GetCommandLineW () akan memberi Anda 2.
LRN
7
OMG, ini berantakan sekali. sup khas MS. tidak ada yang dikanonikalisasi, dan tidak pernah KISS dihormati di dunia MS.
v.oddou
1
Saya memposting versi lintas platform dari implementasi MSVCRT yang diterjemahkan Microsoft dan perkiraan akurasi tinggi menggunakan Regex. Saya tahu ini sudah tua, tapi hei - tidak ada gulungan tubuh.
TylerY86
101

Ini mengganggu saya karena 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 penyuluhan yang diperlukan. Oke, Anda membujuk saya ke dalamnya ...

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

Ini mungkin menghasilkan beberapa string kosong tergantung pada situasinya, tetapi mungkin informasi itu akan berguna dalam kasus lain, jadi saya tidak menghapus entri kosong dalam fungsi ini.

Kedua (dan lebih biasa) pembantu kecil yang akan memangkas pasangan kutipan yang cocok dari awal dan akhir string. Ini lebih rumit daripada metode Trim standar - ini hanya akan memangkas satu karakter dari setiap ujung, dan tidak akan memotong 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 rasa Anda juga menginginkan beberapa tes. Baiklah kalau begitu. Tapi ini pasti hal terakhir! Pertama, fungsi pembantu yang membandingkan hasil pemisahan dengan konten array 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]);
    }

Kemudian 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:""[email protected]"" tasks:""SomeTask,Some Other Task"" -someParam",
             @"/src:""C:\tmp\Some Folder\Sub Folder""", @"/users:""[email protected]""", @"tasks:""SomeTask,Some Other Task""", @"-someParam");

Perhatikan bahwa implementasi memiliki fitur tambahan yang akan menghapus kutipan di sekitar argumen jika itu masuk akal (berkat fungsi TrimMatchingQuotes). Saya percaya itu adalah bagian dari interpretasi baris perintah normal.

Daniel Earwicker
sumber
Saya harus menghapus tanda ini sebagai jawaban karena saya tidak memiliki keluaran yang diharapkan dengan benar. Keluaran sebenarnya tidak boleh memiliki "dalam larik terakhir
Anton
16
Saya datang ke Stack Overflow untuk menghindari persyaratan yang selalu berubah! :) Anda bisa menggunakan Replace ("\" "," ") daripada TrimMatchingQuotes () untuk menghilangkan semua tanda kutip. Tetapi Windows mendukung \" untuk memungkinkan karakter kutipan dilewati. Fungsi Split saya tidak dapat melakukan itu.
Daniel Earwicker
1
Bagus sekali Earwicker :) Anton: Ini adalah solusi yang saya coba jelaskan kepada Anda di posting saya sebelumnya, tetapi Earwicker melakukan pekerjaan yang jauh lebih baik dalam menuliskannya;) Dan juga banyak memperluasnya;)
Israr Khan
spasi bukan satu-satunya karakter pemisah untuk argumen baris perintah, bukan?
Louis Rhys
@ Louis Rhys - Saya tidak yakin. Jika itu yang menjadi perhatian, maka cukup mudah untuk menyelesaikannya: gunakan char.IsWhiteSpacesebagai pengganti== ' '
Daniel Earwicker
25

Parser baris perintah Windows berperilaku seperti yang Anda katakan, membagi ruang kecuali ada kutipan yang tidak ditutup sebelumnya. Saya akan merekomendasikan Anda untuk menulis pengurai 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');
    }
Jeffrey L. Whitledge
sumber
2
Saya berakhir dengan hal yang sama, kecuali saya menggunakan .Split (new char [] {'\ n'}, StringSplitOptions.RemoveEmptyEntries) di baris terakhir jika ada tambahan di antara params. Sepertinya bekerja.
Anton
3
Saya berasumsi Windows harus memiliki cara untuk menghindari tanda kutip di parameter ... algoritma ini tidak memperhitungkannya.
pemimpin
Menghapus baris kosong, menghapus tanda kutip luar, dan menangani tanda kutip yang lolos akan ditinggalkan sebagai excersize bagi pembaca.
Jeffrey L Whitledge
Char.IsWhiteSpace () dapat membantu di sini
Sam Mackrill
Solusi ini bagus jika Argumen dipisahkan oleh spasi tunggal, tetapi gagal adalah argumen dipisahkan oleh beberapa spasi. Tautan ke solusi yang benar: stackoverflow.com/a/59131568/3926504
Dilip Nannaware
13

Saya mengambil jawaban dari Jeffrey L Whitledge dan meningkatkannya sedikit.

Sekarang mendukung tanda kutip tunggal dan ganda. Anda dapat menggunakan tanda kutip di parameter itu sendiri dengan menggunakan tanda kutip lain yang diketik.

Ini juga menghapus 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);
    }
Vapor in the Alley
sumber
7

The baik dan solusi dikelola murni oleh Earwicker gagal menangani argumen seperti ini:

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

Ini mengembalikan 3 elemen:

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

Jadi berikut ini perbaikan untuk mendukung "quote \" 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));
}

Diuji dengan 2 kasus tambahan:

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

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

He whispered to her \ 
I 
love 
you". 

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

Kevin Thach
sumber
3
Maaf atas necromancy tetapi solusi ini masih merindukan hal-hal seperti bla.exe aAAA"b\"ASDS\"c"dSADSDyang menghasilkan di aAAAb"ASDS"cdSADSDmana solusi ini akan keluar aAAA"b"ASDS"c"dSADSD. Saya mungkin mempertimbangkan untuk mengubah TrimMatchingQuotesmenjadi Regex("(?<!\\\\)\\\"")dan menggunakannya seperti ini .
Scis
4

Environment.GetCommandLineArgs ()

Mark Cidade
sumber
2
Berguna - tetapi ini hanya akan memberi Anda argumen baris perintah yang dikirim ke proses saat ini. Persyaratannya adalah mendapatkan string [] dari string "dengan cara yang sama seperti C # jika perintah telah ditentukan pada baris perintah". Saya kira kita bisa menggunakan decompiler untuk melihat bagaimana MS mengimplementasikan ini ...
rohancragg
Seperti yang juga ditemukan Jon Galloway ( weblogs.asp.net/jgalloway/archive/2006/09/13/… ) decompiler tidak banyak membantu yang membawa kita kembali ke jawaban Atif ( stackoverflow.com/questions/298830/… )
rohancragg
4

Aku seperti iterator, dan saat ini LINQ membuat IEnumerable<String>dengan mudah dapat digunakan sebagai array string, jadi saya mengambil mengikuti semangat jawaban Jeffrey L Whitledge ini 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();
}
Monoman
sumber
3

Dalam pertanyaan Anda, Anda meminta regex, dan saya adalah penggemar berat dan pengguna mereka, jadi ketika saya perlu melakukan argumen yang sama seperti Anda, saya menulis regex saya sendiri setelah mencari-cari dan tidak menemukan solusi sederhana. Saya suka solusi singkat, jadi saya membuatnya dan ini dia:

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

Ini menangani kosong dan kutipan di dalam tanda kutip, dan mengubah "" menjadi ". Jangan ragu untuk menggunakan kodenya!

Thomas Petersson
sumber
3

Oh sial. Itu semua ... Eugh. Tapi ini resmi resmi. Dari Microsoft di C # untuk .NET Core, mungkin hanya windows, mungkin lintas platform, tetapi berlisensi MIT.

Pilih tidbits, deklarasi metode dan komentar penting;

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

Ini adalah kode porting ke NET Inti dari .NET Framework dari apa yang saya asumsikan adalah baik perpustakaan MSVC C atau CommandLineToArgvW.

Inilah upaya setengah hati saya untuk menangani beberapa kejahatan dengan Ekspresi Reguler, dan mengabaikan argumen sedikit pun. Ini sedikit ajaib.

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

Mengujinya sedikit pada keluaran yang dihasilkan dengan aneh. Outputnya cocok dengan persentase yang adil dari apa yang diketik dan dijalankan oleh monyet CommandLineToArgvW.

TylerY86
sumber
1
Ya sepertinya versi C # sudah mati. github.com/dotnet/runtime/blob/master/src/coreclr/src/utilcode/…
TylerY86
1
Kebangkitan waktu terbatas. pastebin.com/ajhrBS4t
TylerY86
2

Ini artikel Kode Proyek adalah apa yang telah digunakan di masa lalu. Ini sedikit kode yang bagus, tetapi mungkin berhasil.

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

Zachary Yates
sumber
Saya mencoba reflektor ke dalam perpustakaan C #, tetapi itu pergi ke panggilan C ++ asli yang saya tidak memiliki kodenya, dan tidak dapat melihat cara untuk memanggil tanpa memanggilnya. Saya juga tidak ingin parsing parsing baris perintah, saya hanya ingin string [].
Anton
Merefleksikan .NET juga tidak membawa saya kemana-mana. Melihat kode sumber Mono menyarankan bahwa pemisahan argumen ini tidak dilakukan oleh CLR tetapi sudah berasal dari sistem operasi. Pikirkan parameter argc, argv dari fungsi utama C. Jadi tidak ada yang bisa digunakan kembali selain OS API.
ygoe
2

Karena saya ingin perilaku yang sama seperti OP (membagi string persis sama dengan windows cmd akan melakukannya) saya menulis banyak kasus uji dan menguji jawaban yang diposting di sini:

    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\"." });

nilai "yang diharapkan" berasal dari langsung mengujinya dengan cmd.exe di komputer saya (Win10 x64) dan program cetak sederhana:

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

Inilah hasilnya:


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

Karena tidak ada jawaban yang tampaknya benar (setidaknya berdasarkan kasus penggunaan saya) di sini adalah solusi saya, saat ini lulus semua kasus uji (tetapi jika ada yang memiliki kasus sudut tambahan (gagal), beri komentar):

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

Kode yang saya gunakan untuk menghasilkan hasil tes dapat ditemukan di sini

Mikescher
sumber
1

Sebuah solusi dikelola murni mungkin bisa membantu. Ada terlalu banyak komentar "masalah" untuk fungsi WINAPI dan tidak tersedia di platform lain. Inilah kode saya yang memiliki perilaku yang terdefinisi dengan baik (yang dapat Anda ubah jika Anda suka).

Ini harus melakukan hal yang sama seperti yang dilakukan .NET / Windows saat memberikan string[] argsparameter itu, dan saya telah membandingkannya dengan sejumlah nilai "menarik".

Ini adalah implementasi mesin keadaan klasik yang mengambil setiap karakter dari string masukan dan menafsirkannya untuk keadaan saat ini, menghasilkan keluaran dan keadaan baru. Negara didefinisikan dalam variabel escape, inQuote, hadQuotedan prevCh, dan output dikumpulkan di currentArgdan args.

Beberapa spesialisasi yang saya temukan melalui percobaan pada command prompt nyata (Windows 7): \\menghasilkan \, \"menghasilkan ", ""dalam rentang kutipan menghasilkan ".

The ^karakter tampaknya ajaib, juga: selalu menghilang ketika tidak dua kali lipat itu. Jika tidak, itu tidak berpengaruh pada baris perintah yang sebenarnya. Implementasi saya tidak mendukung ini, karena saya belum menemukan pola dalam perilaku ini. Mungkin ada yang tahu lebih banyak tentang itu.

Sesuatu yang tidak sesuai dengan pola ini adalah perintah berikut:

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

The cmdperintah tampaknya menangkap tanda kutip luar dan mengambil sisa verbatim. Pasti ada saus ajaib khusus di sini.

Saya tidak melakukan tolok ukur pada metode saya, tetapi menganggapnya cukup cepat. Itu tidak menggunakan Regexdan tidak melakukan penggabungan string apa pun melainkan menggunakan a StringBuilderuntuk mengumpulkan karakter untuk argumen dan menempatkannya dalam daftar.

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

Menggunakan:

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

Berdasarkan jawaban Vapor in the Alley , yang satu ini juga mendukung ^ escapes.

Contoh:

  • ini adalah sebuah ujian
    • ini
    • adalah
    • Sebuah
    • uji
  • ini adalah sebuah ujian
    • ini
    • adalah
    • uji
  • ini ^ "adalah tes ^"
    • ini
    • "adalah
    • Sebuah"
    • uji
  • ini "" "adalah tes ^^"
    • ini
    • </s> </s> </s> </s> </s> </s> </s> </s> </s> </s> </s> </s> </s> </s> </s> </s> </s> </s> </s> </s> </s> </s> </s> </s> </s> </s> orang </s>
    • adalah tes ^

Ini juga mendukung banyak spasi (mematahkan argumen hanya satu kali per blok spasi).

Fabio Iotti
sumber
Yang terakhir dari tiga entah bagaimana mengganggu penurunan harga dan tidak ditampilkan seperti yang dimaksudkan.
Peter Mortensen
Diperbaiki dengan spasi lebar nol.
Fabio Iotti
0

Saat ini, ini adalah kode yang saya miliki:

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

Ini tidak berfungsi dengan kutipan yang lolos, tetapi berfungsi untuk kasus-kasus yang saya hadapi sejauh ini.

Anton
sumber
0

Ini adalah balasan untuk kode Anton, yang tidak berfungsi dengan tanda kutip yang lolos. Saya mengubah 3 tempat.

  1. The konstruktor untuk StringBuilder di SplitCommandLineArguments , menggantikan setiap \" dengan \ r
  2. Pada loop-for di SplitCommandLineArguments , saya sekarang mengganti karakter \ r kembali ke \ " .
  3. Mengubah metode SplitCommandLineArgument dari private menjadi 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;
}
CS.
sumber
Saya menangani masalah yang sama ini, Anda akan berpikir bahwa di zaman sekarang ini solusi sederhana akan ada untuk string argumen baris perintah pengujian unit. Yang ingin saya pastikan adalah perilaku yang akan dihasilkan dari string argumen baris perintah yang diberikan. Saya menyerah untuk saat ini dan akan membuat pengujian unit untuk string [] tetapi mungkin menambahkan beberapa pengujian integrasi untuk menutupinya.
Charlie Barker
0

Saya tidak berpikir ada tanda kutip tunggal atau ^ tanda kutip untuk aplikasi C #. Fungsi berikut berfungsi dengan baik untuk saya:

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

Anda dapat melihat kode yang saya posting kemarin:

[C #] String jalur & argumen

Ini membagi argumen nama file + menjadi string []. Jalur pendek, variabel lingkungan, dan ekstensi file yang hilang ditangani.

(Awalnya untuk UninstallString di Registry.)

Nolmë Informatique
sumber
0

Coba kode ini:

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

Itu ditulis dalam bahasa Portugis.

Lucas De Jesus
sumber
bukan dokumentasinya dalam bahasa Portugis
Enamul Hassan
@EnamulHassan Saya akan mengatakan kode itu juga dalam bahasa Portugis, misalnya posicao_ponteiro += ((fim - posicao_ponteiro)+1);.
MEMark
0

Berikut adalah satu liner yang menyelesaikan pekerjaan (lihat satu baris yang melakukan semua pekerjaan di dalam metode BurstCmdLineArgs (...)).

Bukan yang saya sebut sebagai baris kode yang paling mudah dibaca, tetapi Anda dapat memecahnya demi keterbacaan. Ini sederhana dengan sengaja dan tidak berfungsi dengan baik untuk semua kasus argumen (seperti argumen nama file yang berisi pemisah karakter string terpisah di dalamnya).

Solusi ini telah bekerja dengan baik dalam solusi saya yang menggunakannya. Seperti yang saya katakan, itu menyelesaikan pekerjaan tanpa sarang kode tikus untuk menangani setiap kemungkinan format argumen n-faktorial.

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

Tidak dapat menemukan apa pun yang saya suka di sini. Saya benci mengacaukan tumpukan dengan sihir hasil untuk baris perintah kecil (jika itu adalah aliran terabyte, itu akan menjadi cerita lain).

Ini pendapat saya, ini mendukung pelarian kutipan dengan tanda kutip ganda seperti ini:

param = "a 15" "layar tidak buruk" param2 = 'a 15 "layar tidak buruk' param3 =" "param4 = / param5

hasil:

param = "layar 15" tidak buruk "

param2 = 'layar 15 "tidak buruk'

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

Saya telah menerapkan mesin negara untuk memiliki hasil parser yang sama seolah-olah args akan diteruskan ke dalam aplikasi NET dan diproses dalam static void Main(string[] args)metode.

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

Berikut adalah solusi yang memperlakukan spasi (satu atau beberapa spasi) sebagai pemisah parameter baris perintah dan mengembalikan argumen baris perintah yang sebenarnya:

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');
}
Dilip Nannaware
sumber
0

Ada paket NuGet yang berisi fungsionalitas yang Anda butuhkan:

Microsoft.CodeAnalysis.Common berisi kelas CommandLineParser dengan metode SplitCommandLineIntoArguments .

Anda menggunakannya seperti ini:

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
Robin Hartmann
sumber
-2

Saya tidak yakin apakah saya mengerti Anda, tetapi apakah masalah karakter yang digunakan sebagai pemisah, juga dapat ditemukan di dalam teks? (Kecuali untuk itu, ini di-escape dengan "?)

Jika demikian, saya akan membuat forloop, dan mengganti semua instance di mana <"> ada dengan <|> (atau karakter" aman "lainnya, tetapi pastikan itu hanya menggantikan <">, dan bukan <"">

Setelah iterasi string, saya akan melakukan seperti yang diposting sebelumnya, membagi string, tetapi sekarang pada karakter <|>.

Israr Khan
sumber
Ganda "" adalah karena itu adalah string literal @ "..", Tanda ganda "di dalam string @" .. "setara dengan \ escaped" dalam string normal
Anton
"satu-satunya batasan (saya percaya) adalah bahwa string dibatasi spasi, kecuali jika uccur spasi dalam" ... "blok" -> Mungkin menembak burung dengan bazoka, tetapi letakkan boolean yang berbunyi "benar" ketika di dalam kutipan, dan jika spasi terdeteksi di dalam sementara "benar", lanjutkan, lain <> = <|>
Israr Khan
-6

Ya, objek string memiliki fungsi bawaan Split()yang disebut yang mengambil parameter tunggal yang menentukan karakter yang akan dicari sebagai pemisah, dan mengembalikan larik string (string []) dengan nilai individual di dalamnya.

Charles Bretana
sumber
1
Ini akan membagi bagian src: "C: \ tmp \ Some Folder \ Sub Folder" dengan benar.
Anton
Bagaimana dengan kutipan di dalam string yang untuk sementara menonaktifkan pemisahan spasi?
Daniel Earwicker