Tách chuỗi chứa tham số dòng lệnh thành chuỗi [] trong C #


91

Tôi có một chuỗi đơn chứa các tham số dòng lệnh sẽ được chuyển cho tệp thực thi khác và tôi cần trích xuất chuỗi [] chứa các tham số riêng lẻ theo cách giống như C # nếu các lệnh đã được chỉ định trên dòng lệnh. Chuỗi [] sẽ được sử dụng khi thực thi một điểm vào của hội đồng khác thông qua phản xạ.

Có một chức năng tiêu chuẩn cho điều này? Hoặc có một phương pháp ưa thích (regex?) Để tách các tham số một cách chính xác không? Nó phải xử lý các chuỗi phân tách '' 'có thể chứa khoảng trắng một cách chính xác, vì vậy tôi không thể chỉ tách trên' '.

Chuỗi ví dụ:

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

Kết quả ví dụ:

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

Tôi không cần thư viện phân tích cú pháp dòng lệnh, chỉ là một cách để lấy Chuỗi [] sẽ được tạo.

Cập nhật : Tôi đã phải thay đổi kết quả mong đợi để phù hợp với những gì thực sự được tạo bởi C # (loại bỏ các dấu "thừa trong chuỗi phân tách)



5
Mỗi khi ai đó phản hồi, bạn dường như có ý kiến ​​phản đối dựa trên tài liệu không có trong bài đăng của bạn. Tôi khuyên bạn nên cập nhật bài viết của bạn với tài liệu này. Bạn có thể nhận được câu trả lời tốt hơn.
tvanfosson

1
Câu hỏi hay, tìm kiếm giống nhau. Tôi đã hy vọng tìm thấy ai đó nói "hey .net phơi bày điều đó ở đây ..." :) Nếu tôi bắt gặp điều đó vào một lúc nào đó, tôi sẽ đăng nó ở đây, mặc dù điều này giống như 6 tuổi. Vẫn là một câu hỏi hợp lệ!
MikeJansen

Tôi đã tạo một phiên bản được quản lý hoàn toàn trong câu trả lời bên dưới vì tôi cũng cần chức năng này.
ygoe

Câu trả lời:


75

Ngoài giải pháp tốt và thuần túy được quản lý bởi Earwicker , có thể đáng nói, vì tính hoàn chỉnh, Windows cũng cung cấp CommandLineToArgvWchức năng chia nhỏ một chuỗi thành một mảng chuỗi:

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

Phân tích cú pháp chuỗi dòng lệnh Unicode và trả về một mảng con trỏ đến các đối số dòng lệnh, cùng với số lượng các đối số như vậy, theo cách tương tự như các giá trị argv và argc trong C chạy-time tiêu chuẩn.

Bạn có thể tìm thấy ví dụ về cách gọi API này từ C # và giải nén mảng chuỗi kết quả trong mã được quản lý tại " Chuyển đổi chuỗi dòng lệnh thành chuỗi ký tự [] bằng API CommandLineToArgvW () ." Dưới đây là một phiên bản đơn giản hơn một chút của cùng một mã:

[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
Hàm này yêu cầu bạn thoát khỏi dấu gạch chéo ngược ở cuối của một đường dẫn bên trong dấu ngoặc kép. "C: \ Program Files \" phải là "C: \ Program Files \\" để nó hoạt động phân tích cú pháp chuỗi một cách chính xác.
Magnus Lindhe

8
Nó cũng đáng chú ý là CommandLineArgvW hy vọng đối số đầu tiên là tên chương trình, và sự kỳ diệu phân tích cú pháp được áp dụng là không hoàn toàn giống nếu người ta không được thông qua tại Bạn có thể giả mạo nó với một cái gì đó như:.CommandLineToArgs("foo.exe " + commandLine).Skip(1).ToArray();
Scott Wegner

4
Vì lợi ích của sự hoàn chỉnh, MSVCRT không sử dụng CommandLineToArgvW () để chuyển đổi dòng lệnh thành argc / argv. Nó sử dụng mã riêng của nó, mã này khác. Ví dụ, hãy thử gọi CreateProcess với chuỗi này: a "b c" def. Trong main (), bạn sẽ nhận được 3 đối số (như được ghi trong MSDN), nhưng tổ hợp CommandLineToArgvW () / GetCommandLineW () sẽ cung cấp cho bạn 2 đối số
LRN 14/11/12

7
OMG đây là một mớ hỗn độn. súp MS điển hình. không có gì được chuẩn hóa, và không bao giờ KISS được tôn trọng trong thế giới MS.
v.oddou

1
Tôi đã đăng một phiên bản đa nền tảng của việc triển khai MSVCRT đã được Microsoft dịch và một phép gần đúng có độ chính xác cao bằng cách sử dụng Regex. Tôi biết điều này đã cũ, nhưng này - không có cuộn cơ thể nào.
TylerY86

101

Tôi làm phiền tôi rằng không có chức năng nào để tách một chuỗi dựa trên một chức năng kiểm tra từng ký tự. Nếu có, bạn có thể viết nó như thế này:

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

Mặc dù đã viết như vậy, tại sao không viết các phương thức mở rộng cần thiết. Được rồi, bạn đã nói với tôi về nó ...

Thứ nhất, phiên bản Split của riêng tôi có một hàm phải quyết định xem ký tự được chỉ định có nên chia chuỗi hay không:

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

Nó có thể mang lại một số chuỗi trống tùy thuộc vào tình huống, nhưng có thể thông tin đó sẽ hữu ích trong các trường hợp khác, vì vậy tôi không xóa các mục trống trong hàm này.

Thứ hai (và trần tục hơn) một trình trợ giúp nhỏ sẽ cắt một cặp dấu ngoặc kép phù hợp từ đầu và cuối của một chuỗi. Nó cầu kỳ hơn phương pháp Trim tiêu chuẩn - nó sẽ chỉ cắt một ký tự từ mỗi đầu và sẽ không cắt chỉ từ một đầu:

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

Và tôi cho rằng bạn cũng sẽ muốn một số thử nghiệm. Cũng ổn rồi. Nhưng đây hoàn toàn phải là điều cuối cùng! Đầu tiên, một hàm trợ giúp so sánh kết quả của phép tách với nội dung mảng mong đợi:

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

Sau đó, tôi có thể viết các bài kiểm tra như thế này:

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

Đây là bài kiểm tra cho các yêu cầu của bạn:

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

Lưu ý rằng triển khai có tính năng bổ sung là nó sẽ loại bỏ các dấu ngoặc kép xung quanh một đối số nếu điều đó hợp lý (nhờ vào hàm TrimMatchingQuotes). Tôi tin rằng đó là một phần của cách diễn giải dòng lệnh thông thường.


Tôi đã phải bỏ đánh dấu đây là câu trả lời vì tôi không có kết quả đầu ra đúng như mong đợi. Đầu ra thực tế không được có "s trong mảng cuối cùng
Anton

16
Tôi đến với Stack Overflow để thoát khỏi các yêu cầu luôn thay đổi! :) Bạn có thể sử dụng Replace ("\" "," ") thay vì TrimMatchingQuotes () để loại bỏ tất cả các dấu ngoặc kép. Nhưng Windows hỗ trợ \" để cho phép một ký tự trích dẫn được chuyển qua. Chức năng Split của tôi không thể làm điều đó.
Daniel Earwicker

1
Tốt lắm Earwicker :) Anton: Đây là giải pháp mà tôi đã cố gắng mô tả cho bạn trong bài viết trước đây của tôi, nhưng Earwicker đã làm tốt hơn nhiều trong việc thu gọn nó;) Và cũng mở rộng nó ra rất nhiều;)
Israr Khan

khoảng trắng không phải là ký tự ngăn cách duy nhất cho các đối số dòng lệnh, phải không?
Louis Rhys

@Louis Rhys - Tôi không chắc. Nếu đó là một mối quan tâm nó là khá dễ dàng để giải quyết: sử dụng char.IsWhiteSpacethay vì== ' '
Daniel Earwicker

25

Trình phân tích cú pháp dòng lệnh của Windows hoạt động giống như bạn nói, phân chia theo không gian trừ khi có một câu trích dẫn không công khai trước nó. Tôi khuyên bạn nên tự viết trình phân tích cú pháp. Có thể như thế này:

    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
Tôi đã kết thúc với điều tương tự, ngoại trừ tôi đã sử dụng .Split (new char [] {'\ n'}, StringSplitOptions.RemoveEmptyEntries) trong dòng cuối cùng trong trường hợp có thêm '' giữa các tham số. Có vẻ như đang hoạt động.
Anton

3
Tôi cho rằng Windows phải có cách để thoát khỏi dấu ngoặc kép trong các tham số ... thuật toán này không tính đến điều đó.
rmeador

Việc loại bỏ các dòng trống, loại bỏ các dấu ngoặc kép bên ngoài và xử lý các dấu ngoặc kép đã thoát được để lại như một kích thước cho người đọc.
Jeffrey L Whitledge,

Char.IsWhiteSpace () có thể giúp đây
Sam Mackrill

Giải pháp này tốt nếu các đối số được phân tách bằng một khoảng trắng, nhưng không thành công khi các đối số được phân tách bằng nhiều khoảng trắng. Liên kết đến giải pháp chính xác: stackoverflow.com/a/59131568/3926504
Dilip Nannaware

13

Tôi đã lấy câu trả lời từ Jeffrey L Whitledge và nâng cao nó một chút.

Nó hiện hỗ trợ cả dấu nháy đơn và dấu ngoặc kép. Bạn có thể sử dụng dấu ngoặc kép trong chính các tham số bằng cách sử dụng dấu ngoặc kép đã nhập khác.

Nó cũng loại bỏ các trích dẫn từ các đối số vì chúng không đóng góp vào thông tin đối số.

    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

Giải pháp tốt và thuần túy được quản lý bởi Earwicker không xử lý được các đối số như sau:

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

Nó trả về 3 phần tử:

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

Vì vậy, đây là một bản sửa lỗi để hỗ trợ báo giá "được trích dẫn \" thoát \ "":

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

Đã thử nghiệm với 2 trường hợp bổ sung:

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

Cũng lưu ý rằng câu trả lời được chấp nhận bởi Atif Aziz sử dụng CommandLineToArgvW cũng không thành công. Nó trả về 4 phần tử:

He whispered to her \ 
I 
love 
you". 

Hy vọng điều này sẽ giúp ai đó đang tìm kiếm một giải pháp như vậy trong tương lai.


3
Xin lỗi vì lỗi sai nhưng giải pháp này vẫn bỏ sót những thứ như bla.exe aAAA"b\"ASDS\"c"dSADSDkết quả là aAAAb"ASDS"cdSADSDgiải pháp này sẽ xuất ra ở đâu aAAA"b"ASDS"c"dSADSD. Tôi có thể xem xét việc thay đổi TrimMatchingQuotesthành a Regex("(?<!\\\\)\\\"")và sử dụng nó như thế này .
Scis

4

2
Hữu ích - nhưng điều này sẽ chỉ giúp bạn cung cấp các dòng lệnh được gửi đến quy trình hiện tại. Yêu cầu là lấy một chuỗi [] từ một chuỗi "theo cách giống như C # nếu các lệnh đã được chỉ định trên dòng lệnh". Tôi đoán chúng ta có thể sử dụng một decompiler để xem làm thế nào MS thực hiện mặc dù điều này ...
rohancragg

Như Jon Galloway cũng đã tìm thấy ( weblogs.asp.net/jgalloway/archive/2006/09/13/… ), một trình dịch ngược không giúp được gì nhiều mà đưa chúng ta trở lại ngay câu trả lời của Atif ( stackoverflow.com/questions/298830/… )
rohancragg

4

Tôi như vòng lặp, và ngày nay LINQ làm cho IEnumerable<String>dễ dàng sử dụng như mảng của chuỗi, do đó mất của tôi theo tinh thần của câu trả lời Jeffrey L Whitledge của là (như là một phương pháp mở rộng string):

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

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

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

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

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

3

Trong câu hỏi của bạn, bạn đã yêu cầu một regex và tôi là một người hâm mộ và người dùng lớn của chúng, vì vậy khi tôi cần thực hiện phân tách đối số này giống như bạn, tôi đã viết regex của riêng mình sau khi tìm kiếm khắp nơi và không tìm ra giải pháp đơn giản. Tôi thích các giải pháp ngắn gọn, vì vậy tôi đã tạo một giải pháp và đây là:

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

Nó xử lý các khoảng trống và dấu ngoặc kép bên trong dấu ngoặc kép và chuyển đổi "" thành "kèm theo. Hãy sử dụng mã này!


3

Ôi chao. Tất cả là ... Eugh. Nhưng đây là chính thức hợp pháp. Từ Microsoft trong C # cho .NET Core, có thể chỉ dành cho windows, có thể đa nền tảng, nhưng được MIT cấp phép.

Chọn mẩu tin, khai báo phương pháp và nhận xét đáng chú ý;

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

Đây là mã được chuyển sang .NET Core từ .NET Framework từ những gì tôi giả sử là thư viện MSVC C hoặc CommandLineToArgvW.

Đây là nỗ lực nửa vời của tôi trong việc xử lý một số trò tai quái với Biểu thức chính quy và bỏ qua đối số không bit. Đó là một chút phù hợp.

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

Đã kiểm tra nó một chút về đầu ra được tạo lập dị. Đầu ra của nó phù hợp với một tỷ lệ phần trăm hợp lý của những gì lũ khỉ đã gõ và chạy qua CommandLineToArgvW.



1
Có vẻ như phiên bản C # đã chết. github.com/dotnet/runtime/blob/master/src/coreclr/src/utilcode/…
TylerY86

1
Thời gian hồi sinh có hạn. pastebin.com/ajhrBS4t
TylerY86

2

Bài viết The Code Project này là những gì tôi đã sử dụng trong quá khứ. Đó là một đoạn mã tốt, nhưng nó có thể hoạt động.

Bài viết MSDN này là điều duy nhất tôi có thể tìm thấy giải thích cách C # phân tích cú pháp các đối số dòng lệnh.


Tôi đã thử phản xạ vào thư viện C #, nhưng nó chuyển đến một lệnh gọi C ++ gốc mà tôi không có mã và không thể thấy bất kỳ cách nào để gọi mà không gọi p. Tôi cũng không muốn có thư viện phân tích cú pháp dòng lệnh, tôi chỉ muốn chuỗi [].
Anton

Phản ánh .NET cũng chẳng đưa tôi đến đâu. Nhìn vào Mono nguồn đang gợi ý rằng tách lập luận này không được thực hiện bởi CLR mà đã xuất phát từ hệ điều hành. Hãy nghĩ về các tham số argc, argv của hàm C main. Vì vậy, không có gì để sử dụng lại ngoài API hệ điều hành.
ygoe

1

Một giải pháp được quản lý thuần túy có thể hữu ích. Có quá nhiều nhận xét "vấn đề" cho chức năng WINAPI và nó không khả dụng trên các nền tảng khác. Đây là mã của tôi có một hành vi được xác định rõ ràng (bạn có thể thay đổi nếu muốn).

Nó sẽ hoạt động giống như những gì .NET / Windows làm khi cung cấp string[] argstham số đó và tôi đã so sánh nó với một số giá trị "thú vị".

Đây là một triển khai máy trạng thái cổ điển lấy từng ký tự đơn từ chuỗi đầu vào và diễn giải nó cho trạng thái hiện tại, tạo ra đầu ra và trạng thái mới. Trạng thái được định nghĩa trong các biến escape, inQuote, hadQuoteprevCh, và sản lượng được thu thập trong currentArgargs.

Một số điểm đặc biệt mà tôi đã khám phá ra bằng các thử nghiệm trên dấu nhắc lệnh thực (Windows 7): \\sản xuất \, \"sản xuất ", ""trong phạm vi được trích dẫn ".

Nhân ^vật dường như cũng có phép thuật: nó luôn biến mất khi không nhân đôi nó. Nếu không, nó không có tác dụng trên một dòng lệnh thực. Việc triển khai của tôi không hỗ trợ điều này, vì tôi không tìm thấy mẫu trong hành vi này. Có lẽ ai đó biết thêm về nó.

Một cái gì đó không phù hợp với mẫu này là lệnh sau:

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

Các cmdlệnh dường như nắm bắt những dấu ngoặc kép bên ngoài và lấy phần còn lại đúng nguyên văn. Phải có một số thứ nước sốt ma thuật đặc biệt trong này.

Tôi đã thực hiện không có điểm chuẩn nào cho phương pháp của mình, nhưng hãy xem nó khá nhanh. Nó không sử dụng Regexvà không thực hiện bất kỳ nối chuỗi nào mà thay vào đó sử dụng a StringBuilderđể thu thập các ký tự cho một đối số và đưa chúng vào danh sách.

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

Sử dụng:

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

Dựa trên câu trả lời của Vapor in the Alley , câu trả lời này cũng hỗ trợ ^ thoát ra.

Ví dụ:

  • đây là một bài kiểm tra
    • điều này
    • a
    • kiểm tra
  • đây là một bài kiểm tra
    • điều này
    • là một
    • kiểm tra
  • đây ^ "là một ^" kiểm tra
    • điều này
    • "Là
    • một "
    • kiểm tra
  • "" "đây là một bài kiểm tra ^^"
    • điều này
    • Các bác sĩ cho biết:
    • là một ^ kiểm tra

Nó cũng hỗ trợ nhiều dấu cách (ngắt đối số chỉ một lần trên mỗi khối dấu cách).


Cái cuối cùng trong ba cái bằng cách nào đó cản trở Markdown và không được hiển thị như dự định.
Peter Mortensen

Đã sửa với khoảng trắng không-chiều-rộng.
Fabio Iotti

1

Bởi vì tôi muốn hành vi giống như OP (chia một chuỗi giống hệt như Windows cmd sẽ làm điều đó), tôi đã viết một loạt các trường hợp thử nghiệm và kiểm tra các câu trả lời được đăng ở đây:

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

giá trị "mong đợi" đến từ việc kiểm tra trực tiếp nó bằng cmd.exe trên máy của tôi (Win10 x64) và một chương trình in đơn giản:

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

Đây là những kết quả:


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

Bởi vì không có câu trả lời nào có vẻ đúng (ít nhất là dựa trên trường hợp sử dụng của tôi) đây là giải pháp của tôi, nó hiện vượt qua tất cả các trường hợp thử nghiệm (nhưng nếu ai có trường hợp góc bổ sung (không thành công), vui lòng nhận xét):

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

Có thể tìm thấy mã tôi đã sử dụng để tạo kết quả kiểm tra tại đây


0

Hiện tại, đây là mã mà tôi có:

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

Nó không hoạt động với các dấu ngoặc kép, nhưng nó hoạt động cho các trường hợp mà tôi đã đưa ra cho đến nay.


0

Đây là câu trả lời cho mã Anton, mã này không hoạt động với dấu ngoặc kép. Tôi đã sửa đổi 3 chỗ.

  1. Hàm tạo cho StringBuilder trong SplitCommandLineArguments , thay thế bất kỳ \ " bằng \ r
  2. Trong vòng lặp for trong SplitCommandLineArguments , bây giờ tôi thay thế ký tự \ r trở lại thành \ " .
  3. Đã thay đổi phương thức SplitCommandLineArgument từ tĩnh riêng thành tĩnh công khai .

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

Tôi đang giải quyết vấn đề tương tự này, bạn sẽ nghĩ rằng trong thời đại ngày nay, một giải pháp đơn giản sẽ tồn tại cho các chuỗi đối số dòng lệnh kiểm tra đơn vị. Tất cả những gì tôi muốn chắc chắn là hành vi sẽ là kết quả của một chuỗi đối số dòng lệnh đã cho. Tôi đang từ bỏ và sẽ tạo các bài kiểm tra đơn vị cho chuỗi [] nhưng có thể thêm một số bài kiểm tra tích hợp để giải quyết vấn đề này.
Charlie Barker

0

Tôi không nghĩ có dấu ngoặc kép hoặc dấu ngoặc kép cho các ứng dụng C #. Chức năng sau đang hoạt động tốt đối với tôi:

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

Bạn có thể xem mã mà tôi đã đăng ngày hôm qua:

[C #] Chuỗi đường dẫn và đối số

Nó chia tên tệp + đối số thành chuỗi []. Các đường dẫn ngắn, biến môi trường và phần mở rộng tệp bị thiếu được xử lý.

(Ban đầu nó dành cho UninstallString trong Registry.)


0

Hãy thử mã này:

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

Nó được viết bằng tiếng Bồ Đào Nha.


chứ không phải tài liệu là portuguese
Enamul Hassan

@EnamulHassan Tôi muốn nói rằng mã cũng bằng tiếng Bồ Đào Nha, ví dụ posicao_ponteiro += ((fim - posicao_ponteiro)+1);.
MEMark

0

Đây là một dòng lót hoàn thành công việc (xem một dòng thực hiện tất cả công việc bên trong phương thức BurstCmdLineArgs (...)).

Không phải những gì tôi gọi là dòng mã dễ đọc nhất, nhưng bạn có thể chia nhỏ nó ra để dễ đọc. Nó đơn giản về mục đích và không hoạt động tốt cho tất cả các trường hợp đối số (như các đối số tên tệp có chứa ký tự phân tách chuỗi phân tách trong chúng).

Giải pháp này đã hoạt động tốt trong các giải pháp của tôi sử dụng nó. Như tôi đã nói, nó hoàn thành công việc mà không cần tổ mã lệnh của chuột để xử lý mọi định dạng đối số có thể có là giai thừa n.

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

Không thể tìm thấy bất cứ thứ gì tôi thích ở đây. Tôi ghét làm rối tung ngăn xếp bằng phép thuật lợi nhuận cho một dòng lệnh nhỏ (nếu đó là một dòng terabyte, đó sẽ là một câu chuyện khác).

Đây là cách của tôi, nó hỗ trợ thoát trích dẫn với dấu ngoặc kép như sau:

param = "a 15" "screen isn''t bad" param2 = 'a 15 "screen isn''t bad' param3 =" "param4 = / param5

kết quả:

param = "a 15" màn hình không tệ "

màn hình param2 = 'a 15 "không tệ'

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

Tôi đã triển khai máy trạng thái để có kết quả phân tích cú pháp giống như thể args sẽ được chuyển vào ứng dụng .NET và được xử lý trong static void Main(string[] args)phương thức.

    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

Đây là giải pháp xử lý (các) khoảng trắng (một hoặc nhiều khoảng trắng) làm dấu phân tách tham số dòng lệnh và trả về các đối số dòng lệnh thực:

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

Có một gói NuGet chứa chính xác chức năng bạn cần:

Microsoft.CodeAnalysis.Common chứa lớp CommandLineParser với phương thức SplitCommandLineIntoArguments .

Bạn sử dụng nó như thế này:

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

Tôi không chắc liệu tôi có hiểu bạn không, nhưng vấn đề mà ký tự được sử dụng làm bộ tách, cũng được tìm thấy bên trong văn bản? (Ngoại trừ việc nó được thoát bằng dấu "kép?)

Nếu vậy, tôi sẽ tạo một forvòng lặp và thay thế tất cả các trường hợp mà <"> hiện diện bằng <|> (hoặc một ký tự" an toàn "khác, nhưng hãy đảm bảo rằng nó chỉ thay thế <"> chứ không phải <"">

Sau khi lặp lại chuỗi, tôi sẽ làm như đã đăng trước đó, tách chuỗi, nhưng bây giờ trên ký tự <|>.


Các dấu "" kép là do chuỗi ký tự @ ".." của nó, Các ký tự kép "bên trong chuỗi @" .. "tương đương với \ Escape" trong một chuỗi bình thường
Anton

"hạn chế duy nhất (tôi tin tưởng) là các chuỗi được phân cách bằng dấu cách, trừ khi dấu cách nằm trong một" ... "khối" -> Có thể đang bắn một con chim bằng bazooka, nhưng hãy đặt một boolean để trở thành "true" khi bên trong một câu trích dẫn và nếu một khoảng trắng được phát hiện bên trong khi "true", hãy tiếp tục, nếu không <> = <|>
Israr Khan.

-6

Có, đối tượng string có một hàm dựng sẵn được gọi là Split()một tham số duy nhất chỉ định ký tự cần tìm làm dấu phân cách và trả về một mảng chuỗi (string []) với các giá trị riêng lẻ trong đó.


1
Điều này sẽ chia phần src: "C: \ tmp \ Some Folder \ Sub Folder" không chính xác.
Anton

Điều gì về dấu ngoặc kép bên trong chuỗi tạm thời tắt tách trên khoảng trắng?
Daniel Earwicker
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.