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)
sumber
Jawaban:
Selain solusi terkelola yang baik dan murni oleh Earwicker , mungkin perlu disebutkan, demi kelengkapan, bahwa Windows juga menyediakan
CommandLineToArgvW
fungsi untuk memecah string menjadi array string: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); } }
sumber
CommandLineToArgs("foo.exe " + commandLine).Skip(1).ToArray();
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.
sumber
char.IsWhiteSpace
sebagai pengganti== ' '
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'); }
sumber
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); }
sumber
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.
sumber
bla.exe aAAA"b\"ASDS\"c"dSADSD
yang menghasilkan diaAAAb"ASDS"cdSADSD
mana solusi ini akan keluaraAAA"b"ASDS"c"dSADSD
. Saya mungkin mempertimbangkan untuk mengubahTrimMatchingQuotes
menjadiRegex("(?<!\\\\)\\\"")
dan menggunakannya seperti ini .Environment.GetCommandLineArgs ()
sumber
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 untukstring
):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(); }
sumber
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!
sumber
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
.sumber
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.
sumber
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
sumber
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[] args
parameter 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
,hadQuote
danprevCh
, dan output dikumpulkan dicurrentArg
danargs
.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
cmd
perintah 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
Regex
dan tidak melakukan penggabungan string apa pun melainkan menggunakan aStringBuilder
untuk 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(); }
sumber
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 juga mendukung banyak spasi (mematahkan argumen hanya satu kali per blok spasi).
sumber
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.
sumber
Ini adalah balasan untuk kode Anton, yang tidak berfungsi dengan tanda kutip yang lolos. Saya mengubah 3 tempat.
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; }
sumber
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(); }
sumber
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.)
sumber
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.
sumber
posicao_ponteiro += ((fim - posicao_ponteiro)+1);
.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; } } }
sumber
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:
hasil:
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(); }
sumber
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(); } }
sumber
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'); }
sumber
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
sumber
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
for
loop, 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 <|>.
sumber
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.sumber