แยกสตริงที่มีพารามิเตอร์บรรทัดคำสั่งเป็นสตริง [] ใน C #


91

ฉันมีสตริงเดียวที่มีพารามิเตอร์บรรทัดคำสั่งที่จะส่งผ่านไปยังไฟล์ปฏิบัติการอื่นและฉันจำเป็นต้องแยกสตริง [] ที่มีพารามิเตอร์แต่ละตัวในลักษณะเดียวกับที่ C # จะระบุหากมีการระบุคำสั่งในบรรทัดคำสั่ง สตริง [] จะถูกใช้เมื่อเรียกใช้จุดเริ่มต้นของแอสเซมบลีอื่นผ่านการสะท้อนกลับ

มีฟังก์ชันมาตรฐานสำหรับสิ่งนี้หรือไม่? หรือมีวิธีที่ต้องการ (regex?) ในการแยกพารามิเตอร์อย่างถูกต้องหรือไม่? ต้องจัดการกับสตริงที่คั่นด้วย "" "ซึ่งอาจมีช่องว่างอย่างถูกต้องดังนั้นฉันจึงไม่สามารถแยกบน" "ได้

สตริงตัวอย่าง:

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

ตัวอย่างผลลัพธ์:

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

ฉันไม่ต้องการไลบรารีการแยกวิเคราะห์บรรทัดคำสั่งเพียงแค่วิธีรับ String [] ที่ควรสร้างขึ้น

อัปเดต : ฉันต้องเปลี่ยนผลลัพธ์ที่คาดไว้เพื่อให้ตรงกับสิ่งที่ C # สร้างขึ้นจริง (ลบส่วนพิเศษ "ในสตริงแยก)



5
ทุกครั้งที่มีคนตอบคุณดูเหมือนจะมีการคัดค้านโดยอาศัยเนื้อหาที่ไม่ได้อยู่ในโพสต์ของคุณ ฉันขอแนะนำให้คุณอัปเดตโพสต์ของคุณด้วยเนื้อหานี้ คุณอาจได้รับคำตอบที่ดีกว่า
tvanfosson

1
คำถามที่ดีกำลังมองหาเหมือนกัน ก็หวังว่าจะเจอคนพูดว่า "เฮ้. net เปิดโปงนั่นนี่ ... " :) ถ้าเจอแบบนั้นเดี๋ยวจะโพสต์ไว้ที่นี่แม้จะอายุ 6 ขวบแล้วก็ตาม ยังคงเป็นคำถามที่ถูกต้อง!
MikeJansen

ฉันได้สร้างเวอร์ชันที่มีการจัดการอย่างหมดจดในคำตอบด้านล่างเนื่องจากฉันต้องการฟังก์ชันนี้ด้วย
ygoe

คำตอบ:


76

นอกเหนือจากโซลูชันที่มีการจัดการที่ดีและบริสุทธิ์โดยEarwickerแล้วอาจเป็นเรื่องที่ควรค่าแก่การกล่าวถึงเพื่อความสมบูรณ์ Windows ยังมีCommandLineToArgvWฟังก์ชันสำหรับการแยกสตริงออกเป็นอาร์เรย์ของสตริง:

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

แยกวิเคราะห์สตริงบรรทัดคำสั่ง Unicode และส่งกลับอาร์เรย์ของพอยน์เตอร์ไปยังอาร์กิวเมนต์บรรทัดคำสั่งพร้อมกับจำนวนอาร์กิวเมนต์ดังกล่าวในลักษณะที่คล้ายกับค่า Argv และ argc รันไทม์ของ C มาตรฐาน

ตัวอย่างของการเรียก API นี้จาก C # และการคลายสตริงอาร์เรย์ผลลัพธ์ในโค้ดที่มีการจัดการสามารถพบได้ที่“ การแปลงสตริงบรรทัดคำสั่งเป็น Args [] โดยใช้ CommandLineToArgvW () API ” ด้านล่างนี้เป็นโค้ดรุ่นเดียวกันที่ง่ายกว่าเล็กน้อย:

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

1
ฟังก์ชันนี้ต้องการให้คุณหลีกเลี่ยงเครื่องหมายแบ็กสแลชต่อท้ายของเส้นทางภายในเครื่องหมายคำพูด "C: \ Program Files \" ต้องเป็น "C: \ Program Files \\" เพื่อให้ฟังก์ชันนี้แยกวิเคราะห์สตริงได้อย่างถูกต้อง
Magnus Lindhe

8
นอกจากนี้ยังเป็นที่น่าสังเกตว่า CommandLineArgvW คาดว่าอาร์กิวเมนต์แรกจะเป็นชื่อโปรแกรมและการใช้เวทมนตร์ในการแยกวิเคราะห์จะไม่เหมือนกันมากนักหากไม่มีการส่งผ่านคุณสามารถปลอมด้วยสิ่งต่างๆเช่น:CommandLineToArgs("foo.exe " + commandLine).Skip(1).ToArray();
Scott Wegner

4
เพื่อความสมบูรณ์ MSVCRT ไม่ใช้ CommandLineToArgvW () เพื่อแปลงบรรทัดคำสั่งเป็น argc / argv มันใช้รหัสของตัวเองซึ่งแตกต่างกัน ตัวอย่างเช่นลองเรียก CreateProcess ด้วยสตริงนี้: a "b c" def ใน main () คุณจะได้รับ 3 อาร์กิวเมนต์ (ตามเอกสารใน MSDN) แต่คอมโบ CommandLineToArgvW () / GetCommandLineW () จะให้ 2
LRN

7
โอ้พระเจ้านี่มันช่างเป็นระเบียบ ซุป MS ทั่วไป ไม่มีสิ่งใดเป็นที่ยอมรับและไม่เคยนับถือ KISS ในโลก MS
v.oddou

1
ฉันโพสต์เวอร์ชันข้ามแพลตฟอร์มของการใช้งาน MSVCRT ที่แปลโดย Microsoft และการประมาณที่มีความแม่นยำสูงโดยใช้ Regex ฉันรู้ว่ามันเก่า แต่เดี๋ยวก่อน - ไม่มีการเลื่อนร่างกาย
TylerY86

101

มันทำให้ฉันรำคาญที่ไม่มีฟังก์ชันในการแยกสตริงตามฟังก์ชันที่ตรวจสอบอักขระแต่ละตัว หากมีคุณสามารถเขียนได้ดังนี้:

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

แม้ว่าจะเขียนไว้แล้วทำไมไม่เขียนวิธีการขยายที่จำเป็น โอเคคุณพูดให้ฉันฟัง ...

ประการแรกเวอร์ชัน Split ของฉันเองที่ใช้ฟังก์ชันที่ต้องตัดสินใจว่าอักขระที่ระบุควรแยกสตริง:

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

มันอาจให้สตริงว่างเปล่าขึ้นอยู่กับสถานการณ์ แต่บางทีข้อมูลนั้นอาจมีประโยชน์ในกรณีอื่น ๆ ดังนั้นฉันจึงไม่ลบรายการว่างในฟังก์ชันนี้

ประการที่สอง (และอื่น ๆ ทั่วไป) ผู้ช่วยตัวน้อยที่จะตัดคู่คำพูดที่ตรงกันจากจุดเริ่มต้นและจุดสิ้นสุดของสตริง มันยุ่งยากกว่าวิธีการตัดแต่งมาตรฐาน - มันจะตัดเพียงหนึ่งตัวจากปลายแต่ละด้านและจะไม่ตัดจากปลายเพียงด้านเดียว:

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

และฉันคิดว่าคุณจะต้องการการทดสอบบางอย่างเช่นกัน เอาล่ะแล้ว แต่นี่ต้องเป็นสิ่งสุดท้ายอย่างแน่นอน! อันดับแรกฟังก์ชันตัวช่วยที่เปรียบเทียบผลลัพธ์ของการแบ่งกับเนื้อหาอาร์เรย์ที่คาดไว้:

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

จากนั้นฉันสามารถเขียนการทดสอบได้ดังนี้:

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

นี่คือการทดสอบสำหรับความต้องการของคุณ:

        Test(@"/src:""C:\tmp\Some Folder\Sub Folder"" /users:""abcdefg@hijkl.com"" tasks:""SomeTask,Some Other Task"" -someParam",
             @"/src:""C:\tmp\Some Folder\Sub Folder""", @"/users:""abcdefg@hijkl.com""", @"tasks:""SomeTask,Some Other Task""", @"-someParam");

โปรดทราบว่าการใช้งานมีคุณสมบัติพิเศษที่จะลบเครื่องหมายคำพูดรอบอาร์กิวเมนต์หากเหมาะสม (ขอบคุณฟังก์ชัน TrimMatchingQuotes) ฉันเชื่อว่านั่นเป็นส่วนหนึ่งของการตีความบรรทัดคำสั่งปกติ


ฉันต้องยกเลิกการทำเครื่องหมายที่นี่เป็นคำตอบเพราะฉันไม่มีผลลัพธ์ที่คาดหวังที่ถูกต้อง เอาต์พุตจริงไม่ควรมี "อยู่ในอาร์เรย์สุดท้าย
Anton

16
ฉันมาที่ Stack Overflow เพื่อหลีกหนีจากข้อกำหนดที่เปลี่ยนแปลงตลอดเวลา! :) คุณสามารถใช้ Replace ("\" "," ") แทน TrimMatchingQuotes () เพื่อกำจัดเครื่องหมายคำพูดทั้งหมด แต่ Windows สนับสนุน \" เพื่อให้สามารถส่งผ่านอักขระเครื่องหมายคำพูดได้ ฟังก์ชัน Split ของฉันไม่สามารถทำได้
Daniel Earwicker

1
หนึ่งใน Earwicker ที่ดี :) Anton: นี่เป็นวิธีแก้ปัญหาที่ฉันพยายามอธิบายให้คุณฟังในโพสต์ก่อนหน้าของฉัน แต่ Earwicker ทำงานได้ดีกว่ามากในการเขียนมันลง;) และยังขยายมันออกไปอีกมาก;)
Israr Khan

ช่องว่างไม่ใช่อักขระคั่นเพียงตัวเดียวสำหรับอาร์กิวเมนต์บรรทัดคำสั่งใช่หรือไม่?
Louis Rhys

@ หลุยส์ริส - ฉันไม่แน่ใจ หากเป็นข้อกังวลก็ค่อนข้างง่ายที่จะแก้ไข: ใช้char.IsWhiteSpaceแทน== ' '
Daniel Earwicker

25

ตัวแยกวิเคราะห์บรรทัดคำสั่งของ Windows จะทำงานเหมือนกับที่คุณพูดโดยแบ่งเป็นช่องว่างเว้นแต่จะมีเครื่องหมายคำพูดที่ไม่ได้ปิดไว้ก่อน ฉันอยากจะแนะนำให้เขียนตัวแยกวิเคราะห์ด้วยตัวคุณเอง สิ่งนี้อาจจะ:

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

2
ฉันลงเอยด้วยสิ่งเดียวกัน excepy ฉันใช้. แยก (ถ่านใหม่ [] {'\ n'}, StringSplitOptions.RemoveEmptyEntries) ในบรรทัดสุดท้ายในกรณีที่มี '' พิเศษระหว่างพารามิเตอร์ ดูเหมือนจะใช้งานได้
Anton

3
ฉันถือว่า Windows ต้องมีวิธีหลีกเลี่ยงเครื่องหมายคำพูดในพารามิเตอร์ ... อัลกอริทึมนี้ไม่ได้คำนึงถึงสิ่งนั้น
rmeador

การลบบรรทัดว่างการลบเครื่องหมายคำพูดภายนอกและการจัดการเครื่องหมายคำพูดที่ใช้ Escape จะถูกปล่อยให้เป็นขนาดพิเศษสำหรับผู้อ่าน
Jeffrey L Whitledge

Char. IsWhiteSpace () สามารถช่วยได้ที่นี่
Sam Mackrill

วิธีนี้ใช้ได้ดีถ้าอาร์กิวเมนต์ถูกคั่นด้วยช่องว่างเดียว แต่ความล้มเหลวคืออาร์กิวเมนต์ถูกคั่นด้วยช่องว่างหลายช่อง ลิงก์ไปยังวิธีแก้ไขที่ถูกต้อง: stackoverflow.com/a/59131568/3926504
Dilip Nannaware

13

ฉันได้รับคำตอบจาก Jeffrey L Whitledgeและปรับปรุงมันเล็กน้อย

ตอนนี้รองรับทั้งคำพูดเดี่ยวและคู่ คุณสามารถใช้เครื่องหมายคำพูดในพารามิเตอร์เองได้โดยใช้เครื่องหมายคำพูดอื่น ๆ

นอกจากนี้ยังตัดเครื่องหมายคำพูดออกจากอาร์กิวเมนต์เนื่องจากสิ่งเหล่านี้ไม่ได้นำไปสู่ข้อมูลการโต้แย้ง

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

7

ที่ดีและวิธีการแก้ปัญหาการจัดการบริสุทธิ์โดยEarwickerล้มเหลวในการจับการขัดแย้งเช่นนี้

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

มันส่งคืน 3 องค์ประกอบ:

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

ดังนั้นนี่คือการแก้ไขเพื่อรองรับ "quoted \" 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));
}

ทดสอบกับ 2 กรณีเพิ่มเติม:

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

นอกจากนี้ยังตั้งข้อสังเกตว่าคำตอบที่ยอมรับโดยAtif Azizซึ่งใช้CommandLineToArgvWก็ล้มเหลวเช่นกัน มันส่งคืน 4 องค์ประกอบ:

He whispered to her \ 
I 
love 
you". 

หวังว่านี่จะช่วยคนที่กำลังมองหาวิธีแก้ปัญหาดังกล่าวในอนาคต


3
ขออภัยสำหรับเวทมนตร์ แต่การแก้ปัญหานี้ยังคงบอลเฉียงสิ่งที่ต้องการbla.exe aAAA"b\"ASDS\"c"dSADSDซึ่งผลในการที่จะแก้ปัญหานี้เอาท์พุทaAAAb"ASDS"cdSADSD aAAA"b"ASDS"c"dSADSDฉันอาจพิจารณาเปลี่ยนTrimMatchingQuotesไปRegex("(?<!\\\\)\\\"")และใช้งานได้ เช่นนี้
Scis

5

ฉันชอบตัวทำซ้ำและในปัจจุบันLINQทำให้IEnumerable<String>ใช้งานได้ง่ายเหมือนกับอาร์เรย์ของสตริงดังนั้นสิ่งที่ฉันทำตามเจตนารมณ์ของคำตอบของJeffrey L Whitledgeคือ (เป็นวิธีการขยายเพื่อstring):

public static IEnumerable<string> ParseArguments(this string commandLine)
{
    if (string.IsNullOrWhiteSpace(commandLine))
        yield break;

    var sb = new StringBuilder();
    bool inQuote = false;
    foreach (char c in commandLine) {
        if (c == '"' && !inQuote) {
            inQuote = true;
            continue;
        }

        if (c != '"' && !(char.IsWhiteSpace(c) && !inQuote)) {
            sb.Append(c);
            continue;
        }

        if (sb.Length > 0) {
            var result = sb.ToString();
            sb.Clear();
            inQuote = false;
            yield return result;
        }
    }

    if (sb.Length > 0)
        yield return sb.ToString();
}

4

2
มีประโยชน์ - แต่จะทำให้คุณได้รับ Args บรรทัดคำสั่งที่ส่งไปยังกระบวนการปัจจุบันเท่านั้น ข้อกำหนดคือการรับสตริง [] จากสตริง "ในลักษณะเดียวกับที่ C # จะระบุหากมีการระบุคำสั่งในบรรทัดคำสั่ง" ฉันเดาว่าเราสามารถใช้ตัวถอดรหัสเพื่อดูว่า MS ใช้งานสิ่งนี้ได้อย่างไร ...
rohancragg

ตามที่ Jon Galloway พบ ( weblogs.asp.net/jgalloway/archive/2006/09/13/… ) ตัวถอดรหัสไม่ได้ช่วยอะไรมากซึ่งจะทำให้เรากลับไปที่คำตอบของ Atif ได้ทันที ( stackoverflow.com/questions/298830/… )
rohancragg

3

ในคำถามของคุณที่คุณถามหา regex และฉันเป็นแฟนตัวยงและเป็นผู้ใช้ดังนั้นเมื่อฉันต้องการแยกอาร์กิวเมนต์แบบเดียวกับคุณฉันจึงเขียน regex ของตัวเองหลังจาก googling ไปรอบ ๆ และไม่ได้หาวิธีง่ายๆ ฉันชอบวิธีแก้ปัญหาสั้น ๆ ดังนั้นฉันจึงทำขึ้นมาและนี่คือ:

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

จัดการช่องว่างและเครื่องหมายคำพูดภายในเครื่องหมายคำพูดและแปลง "" เป็น "ที่อยู่ในกรอบอย่าลังเลที่จะใช้รหัส!


3

โอ้ห่า. มันคือทั้งหมด ... แต่นี่เป็นทางการ จาก Microsoft ใน C # สำหรับ. NET Core อาจเป็น Windows เท่านั้นอาจข้ามแพลตฟอร์ม แต่ได้รับใบอนุญาตจาก MIT

เลือกเกร็ดเล็กเกร็ดน้อยการประกาศวิธีการและความคิดเห็นที่น่าสนใจ

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

นี่คือรหัสที่พอร์ตไปยัง. NET Core จาก. NET Framework จากสิ่งที่ฉันคิดว่าเป็นไลบรารี MSVC C หรือCommandLineToArgvW.

นี่คือความพยายามครึ่งๆกลางๆของฉันในการจัดการบางส่วนของ shenanigans ด้วย Regular Expressions และไม่สนใจอาร์กิวเมนต์เป็นศูนย์ มันเป็นเรื่องเล็กน้อย

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

ทดสอบว่ามันค่อนข้างยุติธรรมกับผลลัพธ์ที่สร้างขึ้นแปลกประหลาด CommandLineToArgvWมันเอาท์พุทตรงร้อยละยุติธรรมของสิ่งที่ลิงที่พิมพ์ขึ้นและวิ่งผ่าน



1
ใช่ดูเหมือนเวอร์ชัน C # จะตายไปแล้ว github.com/dotnet/runtime/blob/master/src/coreclr/src/utilcode/…
TylerY86

1
การฟื้นฟูในเวลา จำกัด pastebin.com/ajhrBS4t
TylerY86

2

บทความThe Code Project นี้คือสิ่งที่ฉันเคยใช้ในอดีต เป็นรหัสที่ดี แต่อาจใช้งานได้

นี้บทความ MSDNเป็นสิ่งเดียวที่ฉันสามารถหาที่อธิบายถึงวิธี C # จะแยกวิเคราะห์คำสั่งอาร์กิวเมนต์บรรทัด


ฉันลองใช้ตัวสะท้อนแสงในไลบรารี C # แต่มันไปที่การเรียก C ++ ดั้งเดิมที่ฉันไม่มีรหัสและมองไม่เห็นวิธีใด ๆ ในการโทรโดยไม่ต้องเรียกใช้ p ฉันไม่ต้องการไลบรารีการแยกวิเคราะห์บรรทัดคำสั่งฉันแค่ต้องการสตริง []
Anton

NET ก็ทำให้ฉันไม่มีที่ไหนเหมือนกัน มองเข้าไปในโมโน แหล่ง รหัส แนะนำว่าแยกเรื่องนี้ไม่ได้ทำโดยการ CLR แต่แล้วมาจากระบบปฏิบัติการ ลองนึกถึงพารามิเตอร์ argc, argv ของฟังก์ชันหลัก C ดังนั้นจึงไม่มีอะไรให้นำกลับมาใช้ใหม่นอกจาก OS API
ygoe

2

เพราะฉันต้องการพฤติกรรมเดียวกันกับ OP (แยกสตริงเหมือนกับที่ windows cmd จะทำ) ฉันจึงเขียนกรณีทดสอบจำนวนมากและทดสอบคำตอบที่โพสต์ไว้ที่นี่:

    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:""abcdefg@hijkl.com"" tasks:""SomeTask,Some Other Task"" -someParam foo", new[] { @"/src:C:\tmp\Some Folder\Sub Folder", @"/users:abcdefg@hijkl.com", @"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\"." });

ค่า "ที่คาดไว้" มาจากการทดสอบโดยตรงกับ cmd.exe บนเครื่องของฉัน (Win10 x64) และโปรแกรมพิมพ์อย่างง่าย:

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

นี่คือผลลัพธ์:


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

เนื่องจากดูเหมือนว่าไม่มีคำตอบที่ถูกต้อง (อย่างน้อยก็ขึ้นอยู่กับกรณีการใช้งานของฉัน) นี่คือวิธีแก้ปัญหาของฉันขณะนี้ผ่านกรณีทดสอบทั้งหมด (แต่ถ้าใครมีกรณีมุมเพิ่มเติม (ไม่ผ่าน) โปรดแสดงความคิดเห็น)

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

รหัสที่ฉันใช้ในการสร้างผลการทดสอบสามารถพบได้ที่นี่


1

วิธีการแก้ปัญหาการจัดการอย่างหมดจดอาจจะเป็นประโยชน์ มีความคิดเห็น "ปัญหา" มากเกินไปสำหรับฟังก์ชัน WINAPI และไม่สามารถใช้งานได้บนแพลตฟอร์มอื่น นี่คือรหัสของฉันที่มีพฤติกรรมที่กำหนดไว้อย่างดี (ซึ่งคุณสามารถเปลี่ยนแปลงได้หากต้องการ)

ควรทำเช่นเดียวกับสิ่งที่. NET / Windows ทำเมื่อระบุstring[] argsพารามิเตอร์นั้นและฉันได้เปรียบเทียบกับค่า "น่าสนใจ" จำนวนหนึ่ง

นี่คือการใช้งานสถานะเครื่องคลาสสิกที่ใช้อักขระเดี่ยวแต่ละตัวจากสตริงอินพุตและตีความเป็นสถานะปัจจุบันสร้างเอาต์พุตและสถานะใหม่ รัฐจะกำหนดไว้ในตัวแปรescape, inQuote, hadQuoteและprevChและการส่งออกจะถูกรวบรวมในและcurrentArgargs

บางส่วนของพิเศษที่ฉันได้ค้นพบจากการทดลองในคำสั่งจริงพรอมต์ (Windows 7): \\การผลิต\, \"การผลิต", ในช่วงที่ยกมาผลิต"""

^ตัวละครที่ดูเหมือนว่าจะมีมนต์ขลังมากเกินไป: มันก็จะหายไปเมื่อไม่เสแสร้งมัน มิฉะนั้นจะไม่มีผลกับบรรทัดคำสั่งจริง การใช้งานของฉันไม่รองรับสิ่งนี้เนื่องจากฉันไม่พบรูปแบบในพฤติกรรมนี้ อาจมีใครรู้เพิ่มเติมเกี่ยวกับเรื่องนี้

สิ่งที่ไม่ตรงกับรูปแบบนี้คือคำสั่งต่อไปนี้:

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

cmdคำสั่งดูเหมือนว่าจะจับคำพูดด้านนอกและใช้เวลาที่เหลือคำต่อคำ ต้องมีซอสวิเศษพิเศษอยู่ในนี้

ฉันไม่ได้ทำการเปรียบเทียบกับวิธีการของฉัน แต่ถือว่าเร็วพอสมควร มันไม่ได้ใช้Regexและไม่ทำการต่อสตริงใด ๆ แต่ใช้ a StringBuilderเพื่อรวบรวมอักขระสำหรับอาร์กิวเมนต์และใส่ไว้ในรายการแทน

/// <summary>
/// Reads command line arguments from a single string.
/// </summary>
/// <param name="argsString">The string that contains the entire command line.</param>
/// <returns>An array of the parsed arguments.</returns>
public string[] ReadArgs(string argsString)
{
    // Collects the split argument strings
    List<string> args = new List<string>();
    // Builds the current argument
    var currentArg = new StringBuilder();
    // Indicates whether the last character was a backslash escape character
    bool escape = false;
    // Indicates whether we're in a quoted range
    bool inQuote = false;
    // Indicates whether there were quotes in the current arguments
    bool hadQuote = false;
    // Remembers the previous character
    char prevCh = '\0';
    // Iterate all characters from the input string
    for (int i = 0; i < argsString.Length; i++)
    {
        char ch = argsString[i];
        if (ch == '\\' && !escape)
        {
            // Beginning of a backslash-escape sequence
            escape = true;
        }
        else if (ch == '\\' && escape)
        {
            // Double backslash, keep one
            currentArg.Append(ch);
            escape = false;
        }
        else if (ch == '"' && !escape)
        {
            // Toggle quoted range
            inQuote = !inQuote;
            hadQuote = true;
            if (inQuote && prevCh == '"')
            {
                // Doubled quote within a quoted range is like escaping
                currentArg.Append(ch);
            }
        }
        else if (ch == '"' && escape)
        {
            // Backslash-escaped quote, keep it
            currentArg.Append(ch);
            escape = false;
        }
        else if (char.IsWhiteSpace(ch) && !inQuote)
        {
            if (escape)
            {
                // Add pending escape char
                currentArg.Append('\\');
                escape = false;
            }
            // Accept empty arguments only if they are quoted
            if (currentArg.Length > 0 || hadQuote)
            {
                args.Add(currentArg.ToString());
            }
            // Reset for next argument
            currentArg.Clear();
            hadQuote = false;
        }
        else
        {
            if (escape)
            {
                // Add pending escape char
                currentArg.Append('\\');
                escape = false;
            }
            // Copy character from input, no special meaning
            currentArg.Append(ch);
        }
        prevCh = ch;
    }
    // Save last argument
    if (currentArg.Length > 0 || hadQuote)
    {
        args.Add(currentArg.ToString());
    }
    return args.ToArray();
}

1

ใช้:

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

จากคำตอบของVapor in the Alleyคำตอบนี้ยังสนับสนุน ^ Escape

ตัวอย่าง:

  • นี่คือการทดสอบ
    • นี้
    • คือ
    • ทดสอบ
  • นี่คือการทดสอบ
    • นี้
    • คือ
    • ทดสอบ
  • นี้ ^ "เป็น ^" ทดสอบ
    • นี้
    • "คือ
    • ก "
    • ทดสอบ
  • "" "นี้เป็นการทดสอบ ^^"
    • นี้
    • เป็นการทดสอบ ^

นอกจากนี้ยังรองรับช่องว่างหลายช่อง (แบ่งอาร์กิวเมนต์เพียงครั้งเดียวต่อช่องว่าง)


สุดท้ายของทั้งสามขัดขวาง Markdown และไม่แสดงผลตามที่ตั้งใจไว้
Peter Mortensen

แก้ไขด้วยช่องว่างที่มีความกว้างเป็นศูนย์
Fabio Iotti

0

ตอนนี้นี่คือรหัสที่ฉันมี:

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

มันใช้ไม่ได้กับเครื่องหมายคำพูดที่ใช้ Escape แต่ใช้ได้กับกรณีที่ฉันเจอ


0

นี่คือการตอบกลับรหัสของ Anton ซึ่งใช้ไม่ได้กับเครื่องหมายคำพูดที่ใช้ Escape ฉันแก้ไขสถานที่ 3 แห่ง

  1. ตัวสร้างสำหรับStringBuilderในSplitCommandLineArgumentsแทนที่\ "ด้วย\ r
  2. ในสำหรับวงในSplitCommandLineArgumentsตอนนี้ผมเปลี่ยน\ rตัวอักษรกลับไป\"
  3. เปลี่ยนSplitCommandLineArgumentวิธีการจากภาคเอกชนที่จะคงที่สาธารณะ

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

ฉันกำลังแก้ไขปัญหาเดียวกันนี้คุณคงคิดว่าในวันนี้และยุคนี้จะมีวิธีแก้ปัญหาง่ายๆสำหรับสตริงอาร์กิวเมนต์บรรทัดคำสั่งการทดสอบหน่วย สิ่งที่ฉันต้องการให้แน่ใจคือพฤติกรรมที่จะเป็นผลมาจากสตริงอาร์กิวเมนต์บรรทัดคำสั่งที่กำหนด ตอนนี้ฉันยอมแพ้และจะสร้างการทดสอบหน่วยสำหรับสตริง [] แต่อาจเพิ่มการทดสอบการผสานรวมบางอย่างเพื่อปกปิดสิ่งนี้
Charlie Barker

0

ฉันไม่คิดว่าจะมีเครื่องหมายคำพูดเดียวหรือ ^ สำหรับแอปพลิเคชัน C # ฟังก์ชันต่อไปนี้ใช้งานได้ดีสำหรับฉัน:

public static IEnumerable<String> SplitArguments(string commandLine)
{
    Char quoteChar = '"';
    Char escapeChar = '\\';
    Boolean insideQuote = false;
    Boolean insideEscape = false;

    StringBuilder currentArg = new StringBuilder();

    // needed to keep "" as argument but drop whitespaces between arguments
    Int32 currentArgCharCount = 0;                  

    for (Int32 i = 0; i < commandLine.Length; i++)
    {
        Char c = commandLine[i];
        if (c == quoteChar)
        {
            currentArgCharCount++;

            if (insideEscape)
            {
                currentArg.Append(c);       // found \" -> add " to arg
                insideEscape = false;
            }
            else if (insideQuote)
            {
                insideQuote = false;        // quote ended
            }
            else
            {
                insideQuote = true;         // quote started
            }
        }
        else if (c == escapeChar)
        {
            currentArgCharCount++;

            if (insideEscape)   // found \\ -> add \\ (only \" will be ")
                currentArg.Append(escapeChar + escapeChar);       

            insideEscape = !insideEscape;
        }
        else if (Char.IsWhiteSpace(c))
        {
            if (insideQuote)
            {
                currentArgCharCount++;
                currentArg.Append(c);       // append whitespace inside quote
            }
            else
            {
                if (currentArgCharCount > 0)
                    yield return currentArg.ToString();

                currentArgCharCount = 0;
                currentArg.Clear();
            }
        }
        else
        {
            currentArgCharCount++;
            if (insideEscape)
            {
                // found non-escaping backslash -> add \ (only \" will be ")
                currentArg.Append(escapeChar);                       
                currentArgCharCount = 0;
                insideEscape = false;
            }
            currentArg.Append(c);
        }
    }

    if (currentArgCharCount > 0)
        yield return currentArg.ToString();
}

0

คุณสามารถดูรหัสที่ฉันโพสต์เมื่อวานนี้:

[C #] เส้นทางและสตริงอาร์กิวเมนต์

แบ่งชื่อไฟล์ + อาร์กิวเมนต์เป็นสตริง [] เส้นทางสั้นตัวแปรสภาพแวดล้อมและนามสกุลไฟล์ที่ขาดหายไปจะได้รับการจัดการ

(เริ่มต้นสำหรับ UninstallString ใน Registry)


0

ลองใช้รหัสนี้:

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

มันเขียนเป็นภาษาโปรตุเกส


เอกสารค่อนข้างเป็นภาษาโปรตุเกส
Enamul Hassan

@EnamulHassan ฉันจะบอกว่ารหัสนี้เป็นภาษาโปรตุเกสเช่นposicao_ponteiro += ((fim - posicao_ponteiro)+1);กัน
MEMark

0

นี่คือหนึ่งซับที่ทำให้งานเสร็จ (ดูบรรทัดเดียวที่ทำงานทั้งหมดภายในเมธอด BurstCmdLineArgs (... ))

ไม่ใช่สิ่งที่ฉันเรียกว่าบรรทัดรหัสที่อ่านได้มากที่สุด แต่คุณสามารถแยกออกเพื่อประโยชน์ในการอ่านได้ เป็นเรื่องง่ายตามวัตถุประสงค์และทำงานได้ไม่ดีสำหรับทุกกรณีอาร์กิวเมนต์ (เช่นอาร์กิวเมนต์ชื่อไฟล์ที่มีตัวคั่นอักขระสตริงแยกอยู่ในนั้น)

โซลูชันนี้ทำงานได้ดีในโซลูชันของฉันที่ใช้ อย่างที่ฉันพูดมันทำให้งานเสร็จโดยไม่มีรหัสรังของหนูเพื่อจัดการทุกรูปแบบอาร์กิวเมนต์ n-factorial ที่เป็นไปได้

using System;
using System.Collections.Generic;
using System.Linq;

namespace CmdArgProcessor
{
    class Program
    {
        static void Main(string[] args)
        {
            // test switches and switches with values
            // -test1 1 -test2 2 -test3 -test4 -test5 5

            string dummyString = string.Empty;

            var argDict = BurstCmdLineArgs(args);

            Console.WriteLine("Value for switch = -test1: {0}", argDict["test1"]);
            Console.WriteLine("Value for switch = -test2: {0}", argDict["test2"]);
            Console.WriteLine("Switch -test3 is present? {0}", argDict.TryGetValue("test3", out dummyString));
            Console.WriteLine("Switch -test4 is present? {0}", argDict.TryGetValue("test4", out dummyString));
            Console.WriteLine("Value for switch = -test5: {0}", argDict["test5"]);

            // Console output:
            //
            // Value for switch = -test1: 1
            // Value for switch = -test2: 2
            // Switch -test3 is present? True
            // Switch -test4 is present? True
            // Value for switch = -test5: 5
        }

        public static Dictionary<string, string> BurstCmdLineArgs(string[] args)
        {
            var argDict = new Dictionary<string, string>();

            // Flatten the args in to a single string separated by a space.
            // Then split the args on the dash delimiter of a cmd line "switch".
            // E.g. -mySwitch myValue
            //  or -JustMySwitch (no value)
            //  where: all values must follow a switch.
            // Then loop through each string returned by the split operation.
            // If the string can be split again by a space character,
            // then the second string is a value to be paired with a switch,
            // otherwise, only the switch is added as a key with an empty string as the value.
            // Use dictionary indexer to retrieve values for cmd line switches.
            // Use Dictionary::ContainsKey(...) where only a switch is recorded as the key.
            string.Join(" ", args).Split('-').ToList().ForEach(s => argDict.Add(s.Split()[0], (s.Split().Count() > 1 ? s.Split()[1] : "")));

            return argDict;
        }
    }
}

0

ไม่พบสิ่งที่ชอบที่นี่ ฉันเกลียดที่จะทำให้กองซ้อนสับสนด้วยเวทมนตร์ผลตอบแทนสำหรับบรรทัดคำสั่งเล็ก ๆ (ถ้ามันเป็นกระแสของเทราไบต์มันจะเป็นอีกเรื่องหนึ่ง)

นี่คือสิ่งที่ฉันใช้มันรองรับใบเสนอราคาที่มีเครื่องหมายคำพูดคู่ดังนี้:

param = "a 15" "screen ไม่เลว" param2 = 'a 15 "screen isn''t bad' param3 =" "param4 = / param5

ผลลัพธ์:

param = "หน้าจอ 15" ไม่เลว "

param2 = 'หน้าจอ 15 นิ้วไม่เลว'

param3 = ""

param4 =

/ param5

public static string[] SplitArguments(string commandLine)
{
    List<string> args         = new List<string>();
    List<char>   currentArg   = new List<char>();
    char?        quoteSection = null; // Keeps track of a quoted section (and the type of quote that was used to open it)
    char[]       quoteChars   = new[] {'\'', '\"'};
    char         previous     = ' '; // Used for escaping double quotes

    for (var index = 0; index < commandLine.Length; index++)
    {
        char c = commandLine[index];
        if (quoteChars.Contains(c))
        {
            if (previous == c) // Escape sequence detected
            {
                previous = ' '; // Prevent re-escaping
                if (!quoteSection.HasValue)
                {
                    quoteSection = c; // oops, we ended the quoted section prematurely
                    continue;         // don't add the 2nd quote (un-escape)
                }

                if (quoteSection.Value == c)
                    quoteSection = null; // appears to be an empty string (not an escape sequence)
            }
            else if (quoteSection.HasValue)
            {
                if (quoteSection == c)
                    quoteSection = null; // End quoted section
            }
            else
                quoteSection = c; // Start quoted section
        }
        else if (char.IsWhiteSpace(c))
        {
            if (!quoteSection.HasValue)
            {
                args.Add(new string(currentArg.ToArray()));
                currentArg.Clear();
                previous = c;
                continue;
            }
        }

        currentArg.Add(c);
        previous = c;
    }

    if (currentArg.Count > 0)
        args.Add(new string(currentArg.ToArray()));

    return args.ToArray();
}

0

ฉันได้ติดตั้ง state machine เพื่อให้มีผลลัพธ์ของ parser เหมือนกับว่า args จะถูกส่งผ่านไปยังแอปพลิเคชัน. NET และประมวลผลด้วยstatic void Main(string[] args)วิธีการ

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

0

นี่คือโซลูชันที่ถือว่าช่องว่าง (ช่องว่างเดียวหรือหลายช่อง) เป็นตัวคั่นพารามิเตอร์บรรทัดคำสั่งและส่งกลับอาร์กิวเมนต์บรรทัดคำสั่งจริง:

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

0

มีแพ็คเกจ NuGet ซึ่งมีฟังก์ชันที่คุณต้องการ:

Microsoft.CodeAnalysis.Commonมีชั้นCommandLineParserกับวิธีการSplitCommandLineIntoArguments

คุณใช้สิ่งนี้:

using Microsoft.CodeAnalysis;
// [...]
var cli = @"/src:""C:\tmp\Some Folder\Sub Folder"" /users:""abcdefg@hijkl.com"" 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:"abcdefg@hijkl.com"
// tasks:"SomeTask,Some Other Task"
// -someParam
// foo

-2

ฉันไม่แน่ใจว่าฉันเข้าใจคุณหรือไม่ แต่ปัญหาที่ตัวอักษรที่ใช้เป็นตัวแยกนั้นจะพบในข้อความด้วยหรือไม่? (ยกเว้นว่าจะถูก Escape ด้วย double "?)

ถ้าเป็นเช่นนั้นฉันจะสร้างforลูปและแทนที่อินสแตนซ์ทั้งหมดที่ <"> แสดงอยู่ด้วย <|> (หรืออักขระ" ปลอดภัย "อื่น แต่ตรวจสอบให้แน่ใจว่าแทนที่ <"> เท่านั้นไม่ใช่ <"">

หลังจากทำซ้ำสตริงฉันจะทำตามที่โพสต์ไว้ก่อนหน้านี้แยกสตริง แต่ตอนนี้เป็นอักขระ <|>


คู่ "" เป็นเพราะเป็น @ ".. " string literal ส่วนคู่ "ที่อยู่ในสตริง @" .. "จะเทียบเท่ากับ \ Escape" ในสตริงปกติ
Anton

"ข้อ จำกัด เดียว (I beleive) คือสตริงจะคั่นด้วยช่องว่างเว้นแต่ช่องว่างจะอยู่ภายใน" ... "บล็อก" -> อาจจะยิงนกด้วยบาซูก้า แต่ใส่บูลีนที่ "จริง" เมื่ออยู่ในใบเสนอราคาและหากตรวจพบช่องว่างภายในในขณะที่ "จริง" ให้ดำเนินการต่ออื่น ๆ <> = <|>
อิสราร์ข่าน

-6

ใช่ออบเจ็กต์สตริงมีฟังก์ชันในตัวSplit()ที่เรียกว่าใช้พารามิเตอร์เดียวที่ระบุอักขระเพื่อค้นหาเป็นตัวคั่นและส่งคืนอาร์เรย์ของสตริง (สตริง []) พร้อมกับค่าแต่ละค่าในนั้น


1
สิ่งนี้จะแบ่งส่วน src: "C: \ tmp \ Some Folder \ Sub Folder" อย่างไม่ถูกต้อง
Anton

สิ่งที่เกี่ยวกับเครื่องหมายคำพูดภายในสตริงที่ปิดการแบ่งช่องว่างชั่วคราว?
Daniel Earwicker
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.