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

나는 반복기를 좋아하고 요즘 LINQIEnumerable<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, hadQuoteprevCh, 상기 출력에서 수집 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. 생성자모두 StringBuilderSplitCommandLineArguments , 어떤 대체 \ "를 함께 \ 연구
  2. SplitCommandLineArgumentsfor 루프 에서 이제 \ 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.