C #에서 명령 줄 매개 변수가 포함 된 문자열을 string []으로 분할


91

다른 실행 파일에 전달할 명령 줄 매개 변수가 포함 된 단일 문자열이 있으며 명령 줄에 명령이 지정된 경우 C #과 동일한 방식으로 개별 매개 변수가 포함 된 string []을 추출해야합니다. 리플렉션을 통해 다른 어셈블리 진입 점을 실행할 때 string []이 사용됩니다.

이것에 대한 표준 기능이 있습니까? 아니면 매개 변수를 올바르게 분할하기 위해 선호하는 방법 (정규식?)이 있습니까? 공백을 올바르게 포함 할 수있는 ' "'로 구분 된 문자열을 처리해야하므로 ''로 분할 할 수 없습니다.

예제 문자열 :

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 2014

이 기능이 필요했기 때문에 아래 답변에서 순수하게 관리되는 버전을 만들었습니다.
— ygoe

답변:


75

받는 사람 또한 좋은 순수 관리 솔루션 에 의해 Earwicker , 그것은 윈도우도를 제공, 완벽을 위해서 언급 할만큼 가치가 될 수 있습니다CommandLineToArgvW 문자열의 배열로 문자열을 나누는 기능 :

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

유니 코드 명령 줄 문자열을 구문 분석하고 표준 C 런타임 argv 및 argc 값과 유사한 방식으로 이러한 인수의 개수와 함께 명령 줄 인수에 대한 포인터 배열을 반환합니다.

C #에서이 API를 호출하고 관리 코드에서 결과 문자열 배열의 압축을 푸는 예는 " CommandLineToArgvW () API를 사용하여 명령 줄 문자열을 Args []로 변환 "에서 찾을 수 있습니다 . 다음은 동일한 코드의 약간 더 간단한 버전입니다.

[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
OMG 이건 정말 엉망이야. 전형적인 MS 수프. 표준화 된 것은 없으며 MS 세계에서 KISS는 절대로 존중되지 않습니다.
— v.oddou

1
Microsoft에서 번역 한 MSVCRT 구현의 크로스 플랫폼 버전과 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);
    }

상황에 따라 빈 문자열이 생성 될 수 있지만 해당 정보가 다른 경우에 유용 할 수 있으므로이 함수에서 빈 항목을 제거하지 않습니다.

두 번째로 (더 평범하게) 문자열의 시작과 끝에서 일치하는 따옴표 쌍을 잘라내는 작은 도우미입니다. 표준 Trim 방법보다 까다 롭습니다. 각 끝에서 하나의 문자 만 잘라 내고 한쪽 끝에서만 잘라 내지 않습니다.

    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 함수 덕분에). 이것이 일반적인 명령 줄 해석의 일부라고 생각합니다.


올바른 예상 출력이 없었기 때문에 답으로 표시를 해제해야했습니다. 실제 출력은 최종 배열에 "의이 없어야
— 안톤

16
항상 변화하는 요구 사항에서 벗어나기 위해 Stack Overflow에 왔습니다! :) TrimMatchingQuotes () 대신 Replace ( "\" "," ")를 사용하여 모든 따옴표를 제거 할 수 있지만 Windows는 \"를 지원하여 따옴표 문자를 전달할 수 있습니다. 내 분할 기능은 그렇게 할 수 없습니다.
— Daniel Earwicker

1
Nice one Earwicker :) Anton : 이것이 제가 이전 게시물에서 설명하려고했던 솔루션이지만 Earwicker는 그것을 작성하는 데 훨씬 더 나은 일을했습니다.;) 그리고 그것을 많이 확장했습니다.)
— Israr Khan

공백은 명령 줄 인수를 구분하는 유일한 문자가 아닙니다. 그렇죠?
— Louis Rhys

@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
나는 .Split (new char [] { '\ n'}, StringSplitOptions.RemoveEmptyEntries)를 매개 변수 사이에 여분의 '가있는 경우를 대비하여 마지막 줄에 사용했습니다. 작동하는 것 같습니다.
— Anton

3
나는 Windows에 매개 변수의 따옴표를 이스케이프 처리 할 수있는 방법이 있어야한다고 가정합니다.이 알고리즘은이를 고려하지 않습니다.
— rmeador

빈 줄 제거, 외부 따옴표 제거 및 이스케이프 된 따옴표 처리는 독자를위한 엑서사이즈로 남겨집니다.
— Jeffrey L Whitledge

Char.IsWhiteSpace ()는 여기에서 도움이 될 수 있습니다
— Sam Mackrill 2011 년

이 솔루션은 인수가 단일 공백으로 구분되는 경우 유용하지만 인수가 여러 공백으로 구분되는 경우 실패합니다. 올바른 솔루션 링크 : 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\"."

그래서 다음은 "인용 된 \"escape \ "인용구"를 지원하는 수정입니다.

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

또한 CommandLineToArgvW 를 사용 하는 Atif Aziz 의 수락 된 답변 도 실패했습니다. 4 개의 요소를 반환했습니다.

He whispered to her \ 
I 
love 
you". 

이것이 미래에 그러한 솔루션을 찾는 데 도움이되기를 바랍니다.


3
강령술에 대해 미안하지만이 솔루션은 여전히이 솔루션이 출력되는 위치 와 같은 bla.exe aAAA"b\"ASDS\"c"dSADSD것을 놓치고 있습니다. 나는를 a로 변경하는 것을 고려하고 이것을 이와 같이 사용할 수 있습니다. aAAAb"ASDS"cdSADSDaAAA"b"ASDS"c"dSADSDTrimMatchingQuotesRegex("(?<!\\\\)\\\"")
— Scis

4

2
유용하지만 현재 프로세스에 전송 된 명령 줄 인수 만 가져옵니다. 요구 사항은 " 명령 줄에 명령이 지정된 경우 C #과 동일한 방식으로"문자열에서 string []을 가져 오는 것이 었습니다 . 나는 ... 우리는 MS가이 생각을 구현하는 방법을 살펴 디 컴파일러를 사용할 수있는 것 같아요
— rohancragg

Jon Galloway도 발견했듯이 ( weblogs.asp.net/jgalloway/archive/2006/09/13/… ) 디 컴파일러는 Atif의 답변 ( stackoverflow.com/questions/298830/… )으로 돌아가는 데 큰 도움이되지 않습니다.
— rohancragg

4

나는 반복기를 좋아하고 요즘 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();
}

3

귀하의 질문에서 귀하는 정규식을 요청했으며 저는 열렬한 팬이며 사용자이므로 귀하와 동일한 인수 분할을 수행해야 할 때 간단한 해결책을 찾지 않고 인터넷 검색을 한 후 내 자신의 정규식을 작성했습니다. 나는 짧은 솔루션을 좋아하므로 하나를 만들었고 여기에 있습니다.

            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에서 .NET Core 용 C #에서, 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

이것은 MSVC C 라이브러리 또는 .NET Framework에서 .NET Framework에서 .NET Core로 포팅 된 코드 CommandLineToArgvW입니다.

여기에 정규식으로 일부 헛소리를 처리하고 인수 0 비트를 무시하려는 반감이있는 시도가 있습니다. 약간 마법 같습니다.

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

이 코드 프로젝트 기사 는 제가 과거에 사용한 것입니다. 좋은 코드이지만 작동 할 수도 있습니다.

이 MSDN 문서 는 C #에서 명령 줄 인수를 구문 분석하는 방법을 설명하는 유일한 항목입니다.


리플렉터 링을 C # 라이브러리로 시도했지만 코드가없는 네이티브 C ++ 호출로 이동하고 p 호출없이 호출 할 방법을 볼 수 없습니다. 또한 명령 줄 구문 분석 라이브러리를 원하지 않고 string [] 만 원합니다.
— Anton

.NET을 반영하면 나에게도 아무 것도 가져 오지 않았습니다. 으로 찾고 모노 소스 코드 제안 이 인수 분할이 아니라 이미 CLR에 의해 수행하지만되지 않는다는 것은 운영 체제에서 제공됩니다. C 주 함수의 argc, argv 매개 변수를 생각해보십시오. 따라서 OS API 외에는 재사용 할 것이 없습니다.
— ygoe

1

순수하게 관리 솔루션은 도움이 될 수 있습니다. WINAPI 기능에 대한 "문제"주석이 너무 많으며 다른 플랫폼에서는 사용할 수 없습니다. 다음은 잘 정의 된 동작을 가진 내 코드입니다 (원하는 경우 변경할 수 있음).

해당 string[] args매개 변수를 제공 할 때 .NET / Windows가 수행하는 작업과 동일해야 하며 여러 "흥미로운"값과 비교했습니다.

이것은 입력 문자열에서 각 단일 문자를 가져와 현재 상태에 대해 해석하여 출력과 새 상태를 생성하는 고전적인 상태 시스템 구현입니다. 상태 변수가 정의되어 escape, inQuote, hadQuote및 prevCh, 상기 출력에서 수집 currentArg하고 args.

실제 명령 프롬프트 (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 의 답변을 기반으로하여 ^ 이스케이프도 지원합니다.

예 :

  • 이것은 시험이다
    • 이
    • 이다
    • ㅏ
    • 테스트
  • 이것은 "이다"테스트
    • 이
    • 이다
    • 테스트
  • 이 ^ "is a ^"테스트
    • 이
    • "는
    • ㅏ"
    • 테스트
  • 이 "" "는 ^^ 테스트입니다"
    • 이
    • ^ 테스트입니다

또한 여러 공백을 지원합니다 (공백 블록 당 인수를 한 번만 나눕니다).


세 가지 중 마지막은 어떻게 든 Markdown을 방해하고 의도 한대로 렌더링되지 않습니다.
— Peter Mortensen

너비가 0 인 공간으로 수정되었습니다.
— Fabio Iotti

1

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

"예상"값은 내 컴퓨터 (Win10 x64) 및 간단한 인쇄 프로그램에서 cmd.exe를 사용하여 직접 테스트 한 결과입니다.

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

테스트 결과를 생성하는 데 사용한 코드는 여기 에서 찾을 수 있습니다.


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

이스케이프 따옴표로는 작동하지 않지만 지금까지 언급 한 경우에는 작동합니다.


0

이것은 이스케이프 된 따옴표와 함께 작동하지 않는 Anton의 코드에 대한 응답입니다. 3 곳을 수정했습니다.

  1. 생성자 에 모두 StringBuilder 에 SplitCommandLineArguments , 어떤 대체 \ "를 함께 \ 연구
  2. SplitCommandLineArguments 의 for 루프 에서 이제 \ 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;
}

나는이 같은 문제를 다루고 있는데, 현재 시대에 유닛 테스트 명령 줄 인수 문자열을위한 간단한 솔루션이 존재할 것이라고 생각했을 것입니다. 내가 확인하고 싶은 것은 주어진 명령 줄 인수 문자열에서 발생하는 동작입니다. 나는 지금 포기하고 string []에 대한 단위 테스트를 만들 것이지만 이것을 덮기 위해 몇 가지 통합 테스트를 추가 할 수 있습니다.
— 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 #] 경로 및 인수 문자열

파일 이름 + 인수를 string []으로 분할합니다. 짧은 경로, 환경 변수 및 누락 된 파일 확장자가 처리됩니다.

(처음에는 레지스트리의 UninstallString 용이었습니다.)


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 jul.

@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

내가 좋아하는 것을 찾을 수 없습니다. 작은 명령 줄에 대한 yield magic으로 스택을 엉망으로 만드는 것이 싫습니다 (테라 바이트 스트림이라면 다른 이야기가 될 것입니다).

내 생각은 다음과 같이 큰 따옴표로 따옴표 이스케이프를 지원합니다.

param = "a 15" "화면이 나쁘지 않습니다"param2 = 'a 15 "화면이 나쁘지 않습니다'param3 =" "param4 = / param5

결과:

param = "a 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

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 에는 SplitCommandLineIntoArguments 메서드가있는 CommandLineParser 클래스가 포함되어 있습니다. .

다음과 같이 사용합니다.

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

이해가되는지 잘 모르겠지만 스플리터로 사용 된 캐릭터도 텍스트 안에서 발견되는 문제입니까? (단, 이중 "?"로 이스케이프 처리됩니다.)

그렇다면 for 루프를 < ">가있는 모든 인스턴스를 <|> (또는 다른"안전한 "문자로 대체하지만 <" ">가 아닌 <"> 만 대체해야합니다.

문자열을 반복 한 후 이전에 게시 한대로 문자열을 분할하지만 이제는 <|> 문자를 사용합니다.


이중 ""는 @ ".."문자열 리터럴이기 때문입니다. @ ".."문자열 내부의 이중 ""은 일반 문자열에서 \ 이스케이프 된 "와 동일합니다.
— Anton

"유일한 제한은 (믿음)"... "블록 내에서 공백이 발생하지 않는 한 문자열이 공백으로 구분된다는 것입니다."-> 바주카포로 새를 쏠 수 있지만 "true"가되는 부울을 넣습니다. 인용문 안에있을 때 "true"인 동안 공백이 감지되면 계속하십시오. else <> = <|>
— Israr Khan

-6

예, 문자열 객체에는 Split()구분 기호로 찾을 문자를 지정하는 단일 매개 변수를 취하고 개별 값이있는 문자열 배열 (string [])을 리턴하는 내장 함수 가 있습니다.


1
그러면 src : "C : \ tmp \ Some Folder \ Sub Folder"부분이 잘못 분할됩니다.
— Anton

공백 분할을 일시적으로 해제하는 문자열 내부의 따옴표는 어떻습니까?
— Daniel Earwicker
당사 사이트를 사용함과 동시에 당사의 쿠키 정책과 개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.