C # 'da komut satırı parametrelerini içeren dizeyi [] dizesine böl


91

Başka bir yürütülebilir dosyaya iletilecek komut satırı parametrelerini içeren tek bir dizem var ve tek tek parametreleri içeren [] dizesini komut satırında komutlar belirtilmiş olsaydı C # ile aynı şekilde ayıklamam gerekiyor. [] Dizisi yansıma yoluyla başka bir derlemenin giriş noktasını yürütürken kullanılacaktır.

Bunun için standart bir işlev var mı? Veya parametreleri doğru şekilde bölmek için tercih edilen bir yöntem (regex?) Var mı? Boşluklar içerebilecek '"' sınırlandırılmış dizeleri doğru şekilde işlemelidir, bu yüzden '' bölünemem.

Örnek dize:

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

Örnek sonuç:

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

Komut satırı ayrıştırma kitaplığına ihtiyacım yok, sadece oluşturulması gereken String [] 'yi elde etmenin bir yolu.

Güncelleme : Beklenen sonucu, gerçekte C # tarafından üretilenle eşleşecek şekilde değiştirmem gerekiyordu (bölünmüş dizelerdeki fazla "'lar kaldırıldı)



5
Birisi her yanıt verdiğinde, gönderinizde olmayan materyale dayalı bir itirazınız var gibi görünüyor. Gönderinizi bu materyalle güncellemenizi öneririm. Daha iyi cevaplar alabilirsiniz.
tvanfosson

1
Güzel soru, aynısını arıyorum. "Hey .net bunu burada ifşa ediyor ..." diyen birini bulmayı umuyordum :) Bir noktada bununla karşılaşırsam, 6 yaşında olmasına rağmen buraya postalayacağım. Hala geçerli bir soru!
MikeJansen

Bu işleve de ihtiyacım olduğu için aşağıdaki yanıtta tamamen yönetilen bir sürüm oluşturdum.
ygoe

Yanıtlar:


75

Ek olarak iyi ve saf yönetilen çözümü ile Earwicker , Windows da sağladığını, tamlığı uğruna bahsetmemiz olabilir CommandLineToArgvWdizeleri bir diziye bir dize kadar bozduğu için işlevini:

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

Bir Unicode komut satırı dizesini çözümler ve standart C çalışma zamanı argv ve argc değerlerine benzer bir şekilde, bu tür bağımsız değişkenlerin sayısıyla birlikte komut satırı bağımsız değişkenlerine bir işaretçi dizisi döndürür.

Bu API'yi C # 'dan çağırmanın ve elde edilen dize dizisini yönetilen kodda paketinden çıkarmanın bir örneği " CommandLineToArgvW () API kullanarak Komut Satırı Dizesini Args'e Dönüştürme []' de bulunabilir .” Aşağıda aynı kodun biraz daha basit bir versiyonu bulunmaktadır:

[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
Bu işlev, tırnak içindeki bir yolun sondaki ters eğik çizgisinden kaçmanızı gerektirir. Bunun dizeyi doğru şekilde ayrıştırması için "C: \ Program Files \" "C: \ Program Files \\" olmalıdır.
Magnus Lindhe

8
Ayrıca CommandLineArgvW'nin ilk argümanın program adı olmasını beklediğini ve uygulanan ayrıştırma büyüsünün, eğer biri geçilmezse tam olarak aynı olmadığını belirtmek gerekir. CommandLineToArgs("foo.exe " + commandLine).Skip(1).ToArray();
Şuna

4
Tamlık adına, MSVCRT komut satırını argc / argv'ye dönüştürmek için CommandLineToArgvW () kullanmaz. Farklı olan kendi kodunu kullanır. Örneğin, şu dizeyle CreateProcess çağırmayı deneyin: a "b c" def. Main () 'de 3 argüman alırsınız (MSDN'de belgelendiği gibi), ancak CommandLineToArgvW () / GetCommandLineW () combo size 2 verecektir.
LRN

7
Aman Tanrım, bu tam bir karmaşa. tipik MS çorbası. MS dünyasında hiçbir şey standartlaştırılmaz ve KISS'e asla saygı gösterilmez.
v.oddou

1
Microsoft tarafından çevrilmiş MSVCRT uygulamasının çapraz platform sürümünü ve Regex kullanarak yüksek doğruluk oranına sahip bir tahmini yayınladım. Bunun eski olduğunu biliyorum, ama hey - vücut parşömeni yok.
TylerY86

101

Bir dizgeyi her karakteri inceleyen bir işleve göre bölme işlevinin olmaması beni rahatsız ediyor. Varsa şu şekilde yazabilirsin:

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

Bunu yazmış olmanıza rağmen, neden gerekli genişletme yöntemlerini yazmıyorsunuz? Tamam, beni ikna ettin ...

İlk olarak, belirtilen karakterin dizeyi bölmesi gerekip gerekmediğine karar vermesi gereken bir işlevi alan kendi Split sürümüm:

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

Duruma bağlı olarak bazı boş dizeler verebilir, ancak bu bilgi diğer durumlarda faydalı olabilir, bu nedenle bu işlevdeki boş girişleri kaldırmam.

İkincisi (ve daha sıradan olarak), bir dizenin başından ve sonundan eşleşen bir çift tırnak işaretini kesecek küçük bir yardımcı. Standart Kırpma yönteminden daha zahmetlidir - her iki uçtan yalnızca bir karakter keser ve yalnızca bir uçtan kırpılmaz:

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

Ve sanırım bazı testler de isteyeceksiniz. Peki, tamam o zaman. Ama bu kesinlikle son şey olmalı! Öncelikle, bölünmenin sonucunu beklenen dizi içeriğiyle karşılaştıran bir yardımcı işlev:

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

O zaman şöyle testler yazabilirim:

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

İşte ihtiyaçlarınız için test:

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

Uygulamanın, eğer mantıklıysa (TrimMatchingQuotes işlevi sayesinde) bir bağımsız değişken etrafındaki alıntıları kaldıracak ekstra özelliğe sahip olduğuna dikkat edin. Bunun normal komut satırı yorumlamasının bir parçası olduğuna inanıyorum.


Bunun yanıt olarak işaretini kaldırmak zorunda kaldım çünkü beklenen doğru çıktılara sahip değildim. Gerçek çıktının son dizide "'ler olmamalıdır
Anton

16
Her zaman değişen gereksinimlerden uzaklaşmak için Stack Overflow'a geliyorum! :) Tüm alıntılardan kurtulmak için TrimMatchingQuotes () yerine Replace ("\" "," ") kullanabilirsiniz. Ancak Windows, bir tırnak karakterinin geçmesine izin vermek için \" 'yi destekler. Bölme işlevim bunu yapamaz.
Daniel Earwicker

1
İyi bir Earwicker :) Anton: Bu, önceki yazımda size anlatmaya çalıştığım çözümdü, ancak Earwicker bunu yazarken çok daha iyi bir iş çıkardı;) Ve ayrıca çok
genişletti

komut satırı argümanları için tek ayırıcı karakter boşluk değildir, değil mi?
Louis Rhys

@Louis Rhys - Emin değilim. Bu bir char.IsWhiteSpace== ' '
endişeyse

25

Windows komut satırı ayrıştırıcısı, dediğiniz gibi davranır, önünde kapatılmamış bir alıntı yoksa boşlukta bölünür. Ayrıştırıcıyı kendiniz yazmanızı tavsiye ederim. Bunun gibi bir şey belki:

    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
Paramlar arasında fazladan '' lar olması durumunda son satırda .Split (new char [] {'\ n'}, StringSplitOptions.RemoveEmptyEntries) kullandım. Çalışıyor gibi görünüyor.
Anton

3
Windows'un parametrelerdeki alıntılardan kaçmak için bir yolu olması gerektiğini varsayıyorum ... bu algoritma bunu hesaba katmaz.
rmeador

Boş satırların kaldırılması, dış alıntıların kaldırılması ve kaçan alıntıların işlenmesi okuyucu için bir egzersiz olarak bırakılmıştır.
Jeffrey L Whitledge

Char.IsWhiteSpace () burada yardımcı olabilir
Sam Mackrill

Bağımsız değişkenler tek boşlukla ayrılmışsa bu çözüm iyidir, ancak başarısızlık, bağımsız değişkenlerin birden çok boşlukla ayrılmasıdır. Doğru çözüme bağlantı: stackoverflow.com/a/59131568/3926504
Dilip Nannaware

13

Ben aldı Jeffrey L Whitledge cevabını ve biraz geliştirilmiş.

Artık hem tek hem de çift tırnak işaretlerini destekliyor. Diğer yazılan tırnak işaretlerini kullanarak parametrelerin kendisinde tırnak kullanabilirsiniz.

Ayrıca argüman bilgisine katkıda bulunmadıkları için argümanlardan alıntıları çıkarır.

    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

İyi ve saf yönetilen çözüm ile Earwicker böyle sap argümanları başarısız oldu:

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

3 öğe döndürdü:

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

İşte "alıntı \" kaçış \ "teklifini" desteklemek için bir düzeltme:

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 ek durum ile test edildi:

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

Ayrıca CommandLineToArgvW kullanan Atif Aziz tarafından kabul edilen cevabın da başarısız olduğunu kaydetti. 4 öğe döndürdü:

He whispered to her \ 
I 
love 
you". 

Umarım bu, gelecekte böyle bir çözüm arayan birine yardımcı olur.


3
Büyü için özür dilerim, ancak bu çözüm yine de bu çözümün çıktı vereceği bla.exe aAAA"b\"ASDS\"c"dSADSDsonuçlara benzer şeyleri kaçırıyor . Ben değiştirmeyi düşünebilirsiniz a ve kullanımı böyle . aAAAb"ASDS"cdSADSDaAAA"b"ASDS"c"dSADSDTrimMatchingQuotesRegex("(?<!\\\\)\\\"")
Scis

4

2
Faydalıdır - ancak bu size yalnızca mevcut işleme gönderilen komut satırı değiştirgelerini alır. Gereklilik "C # edeceğini aynı şekilde bir dizeden bir dize [] bulmaktı eğer komutlar komut satırında belirtilen edilmişti". Sanırım MS'in bunu nasıl uyguladığına bakmak için bir
derleyici kullanabiliriz


4

Yineleyicileri severim ve günümüzde LINQIEnumerable<String> , dizgi dizileri kadar kolay kullanılabilir hale getirir , bu nedenle Jeffrey L Whitledge'ın cevabının ruhunu takip ettim (bir genişletme yöntemi olarak 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

Sorunuzda bir normal ifade istediniz ve ben onların büyük bir hayranı ve kullanıcısıyım, bu yüzden sizinle aynı argümanı ayırmam gerektiğinde, etrafta dolaşıp basit bir çözüm bulamadan kendi normal ifademi yazdım. Kısa çözümleri severim, bu yüzden bir tane yaptım ve işte burada:

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

Boşlukları ve tırnak işaretleri içindeki tırnak işaretlerini kullanır ve ekteki "" ifadesini dönüştürür. Kodu kullanmaktan çekinmeyin!


3

Oh kahretsin. Hepsi ... Eugh. Ama bu yasal bir resmi. Microsoft'tan .NET Core için C #, belki yalnızca Windows, belki çapraz platform olabilir, ancak MIT lisanslıdır.

Bilgi bitlerini, yöntem bildirimlerini ve dikkate değer yorumları seçin;

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

Bu, MSVC C kitaplığı ya da .NET Framework'ten .NET Core'a taşınan koddur CommandLineToArgvW.

İşte bazı saçmalıkları Normal İfadelerle ele alma ve argümanı sıfır biti görmezden gelme konusundaki gönülsüz girişimim. Biraz sihirbazlık.

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

Tuhaf üretilen çıktı üzerinde biraz test etti. Çıktısı, maymunların yazıp geçtiklerinin makul bir yüzdesiyle eşleşiyor CommandLineToArgvW.



1
Evet, C # sürümü bitmiş gibi görünüyor. github.com/dotnet/runtime/blob/master/src/coreclr/src/utilcode/…
TylerY86

1
Sınırlı bir süre canlanma. pastebin.com/ajhrBS4t
TylerY86

2

Bu Kod Projesi makalesi , geçmişte kullandığım şeydir. İyi bir kod parçası ama işe yarayabilir.

Bu MSDN makalesi , C # komut satırı bağımsız değişkenlerini nasıl ayrıştırdığını açıklayan bulabildiğim tek şey.


C # kitaplığına yansıtmayı denedim, ancak kodumun olmadığı yerel bir C ++ çağrısına gidiyor ve onu p-çağırmadan arama yapmanın hiçbir yolunu göremiyorum. Ayrıca bir komut satırı ayrıştırma kitaplığı istemiyorum, sadece [] dizesini istiyorum.
Anton

.NET'i yansıtmak beni hiçbir yere götürmedi. İçine bakıyor Mono kaynak koduna önerdi bu argüman bölme yerine zaten CLR tarafından yapılır ancak olmadığını işletim sisteminden geliyor. C ana fonksiyonunun argc, argv parametrelerini düşünün. Dolayısıyla, OS API dışında yeniden kullanılacak bir şey yoktur.
ygoe

2

OP ile aynı davranışı istediğim için (bir dizeyi Windows cmd'nin yapacağı gibi böldüm) bir sürü test senaryosu yazdım ve burada yayınlanan cevapları test ettim:

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

"beklenen" değer, doğrudan makinemde (Win10 x64) ve basit bir yazdırma programında cmd.exe ile test edilmesinden gelir:

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

Sonuçlar şunlardır:


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

Hiçbir cevap doğru görünmediğinden (en azından kullanım durumuma göre) işte benim çözümüm, şu anda tüm test durumlarını geçiyor (ancak herhangi birinin ek (başarısız) köşe vakaları varsa lütfen yorum yapın):

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

Test sonuçlarını oluşturmak için kullandığım kod burada bulunabilir


1

Bir tamamen yönetilen çözüm yararlı olabilir. WINAPI işlevi için çok fazla "sorun" yorumu var ve diğer platformlarda mevcut değil. İşte iyi tanımlanmış davranışa sahip kodum (isterseniz değiştirebileceğiniz).

Bu string[] argsparametreyi sağlarken .NET / Windows'un yaptıklarının aynısını yapmalıdır ve ben bunu birkaç "ilginç" değerle karşılaştırdım.

Bu, her bir karakteri giriş dizesinden alan ve onu mevcut durum için yorumlayarak çıktı ve yeni bir durum üreten klasik bir durum makinesi uygulamasıdır. Durum değişkenleri de tanımlandığı escape, inQuote, hadQuoteve prevChve çıkış toplanır currentArgve args.

Ben istemi gerçek komuta deneyler (Windows 7) tarafından keşfedilen ettik uzmanlık bazıları: \\üretir \, \"üreten ", ""bir alıntı aralık üretir içinde ".

^Bunu iki katına zaman değil her zaman kaybolur: karakteri de büyülü gibi görünüyor. Aksi takdirde gerçek bir komut satırı üzerinde hiçbir etkisi yoktur. Bu davranışta bir model bulamadığım için uygulamam bunu desteklemiyor. Belki birisi daha fazlasını biliyordur.

Bu modele uymayan bir şey aşağıdaki komuttur:

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

cmdKomut dış tırnak yakalamak ve kelimesi kelimesine dinlenme gibi görünüyor. Bunun içinde özel bir sihirli sos olmalı.

Yöntemim üzerinde hiçbir kıyaslama yapmadım, ancak makul derecede hızlı düşünün. RegexHerhangi bir dize birleştirme kullanmaz ve yapmaz, bunun yerine StringBuilderbir argümanın karakterlerini toplamak için a kullanır ve bunları bir listeye koyar.

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

Kullanım:

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

Dayanarak Alley içinde Vapor 'ın cevabı, aynı zamanda bu bir destekleri ^ kaçar.

Örnekler:

  • bu bir test
    • bu
    • dır-dir
    • a
    • Ölçek
  • bu bir test
    • bu
    • bir
    • Ölçek
  • bu ^ "bir ^" testidir
    • bu
    • "dır-dir
    • a "
    • Ölçek
  • bu "" "bir ^^ testtir"
    • bu
    • bir ^ testtir

Ayrıca, birden çok boşluğu destekler (argümanları, boşluk bloğu başına yalnızca bir kez kırar).


Üçünden sonuncusu bir şekilde Markdown'a müdahale ediyor ve amaçlandığı gibi sunulmuyor.
Peter Mortensen

Sıfır genişlikli bir boşlukla düzeltildi.
Fabio Iotti

0

Şu anda sahip olduğum kod bu:

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

Kaçan alıntılarla çalışmıyor, ancak şimdiye kadar karşılaştığım davalar için çalışıyor.


0

Bu, Anton'un kaçan alıntılarla çalışmayan koduna bir cevaptır. 3 yeri değiştirdim.

  1. Yapıcı için StringBuilder içinde SplitCommandLineArguments , herhangi bir yerine \" ile \ r
  2. In For döngüsü içinde SplitCommandLineArguments , şimdi yerine \ r karakter geri \" .
  3. Değiştirilen SplitCommandLineArgument gelen yöntemini özel için kamu statik .

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

Ben de aynı sorunu ele alıyorum, bu gün ve çağda komut satırı argüman dizilerini birim test etmek için basit bir çözümün var olacağını düşünürdünüz. Emin olmak istediğim tek şey, belirli bir komut satırı argüman dizesinden kaynaklanacak davranış. Şimdilik pes ediyorum ve string [] için birim testleri oluşturacağım ancak bunu kapatmak için bazı entegrasyon testleri ekleyebilirim.
Charlie Barker 1911

0

C # uygulamaları için tek tırnaklar veya ^ tırnaklar olduğunu sanmıyorum. Aşağıdaki işlev benim için iyi çalışıyor:

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

Bu kodu deneyin:

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

Portekizce yazılmış.


oldukça dokümantasyon portekizce
Enamul Hassan

@EnamulHassan Kodun Portekizce de olduğunu söyleyebilirim, örn posicao_ponteiro += ((fim - posicao_ponteiro)+1);.
MEMark

0

İşte işi bitiren bir satır (BurstCmdLineArgs (...) yöntemi içindeki tüm işi yapan bir satıra bakın).

En okunabilir kod satırı dediğim şey değil, ancak okunabilirlik uğruna onu kırabilirsiniz. Amaç olarak basittir ve tüm argüman durumları için iyi çalışmaz (içlerinde bölünmüş dize karakter sınırlayıcısını içeren dosya adı argümanları gibi).

Bu çözüm, onu kullanan çözümlerimde iyi çalıştı. Dediğim gibi, işi olası her argüman biçimini n-faktöryel işlemek için bir fare kod yuvası olmadan yapar.

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

Burada sevdiğim hiçbir şey bulamadım. Yığını küçük bir komut satırı için verim büyüsüyle karıştırmaktan nefret ediyorum (eğer bir terabaytlık bir akış olsaydı, bu başka bir hikaye olurdu).

İşte benim aldığım, bunlar gibi çift tırnaklı alıntı kaçışlarını destekler:

param = "15" ekran kötü değil "param2 = '15" ekran kötü değil' param3 = "" param4 = / param5

sonuç:

param = "15" ekran fena değil "

param2 = '15 "ekran fena değil'

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

Durum makinesini, args .NET uygulamasına geçirilecek ve static void Main(string[] args)yöntemde işlenecekmiş gibi aynı ayrıştırıcı sonuçlarına sahip olacak şekilde uyguladım .

    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

Boşlukları (tekli veya çoklu boşlukları) komut satırı parametre ayırıcısı olarak değerlendiren ve gerçek komut satırı argümanlarını döndüren çözüm burada:

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

Tam olarak ihtiyacınız olan işlevselliği içeren bir NuGet paketi var:

Microsoft.CodeAnalysis.Common , SplitCommandLineIntoArguments yöntemiyle CommandLineParser sınıfını içerir .

Bunu şu şekilde kullanırsın:

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

Sizi anladığımdan emin değilim ama sorun, ayırıcı olarak kullanılan karakterin metnin içinde de bulunması mı? (Double "ile kaçması dışında?)

Öyleyse, bir fordöngü oluşturur ve <"> 'nin bulunduğu tüm örnekleri <|> (veya başka bir" güvenli "karakterle değiştiririm, ancak <" "> yerine yalnızca <"> yerine geçtiğinden emin olun.

Dizeyi yineledikten sonra, daha önce gönderildiği gibi yapardım, dizeyi bölerdim, ama şimdi <|> karakterinde.


İkili "" ler, @ ".." dizgisi olduğundan, @ ".." dizgesinin içindeki çift ", normal bir dizedeki \ çıkışlı" ile eşdeğerdir
Anton

"tek kısıtlama (sanırım), boşluk bir" ... "blok" içinde olmadıkça dizelerin boşlukla sınırlandırılmış olmasıdır -> Bazukayla bir kuşu vuruyor olabilir, ancak "doğru" olan bir boole koyuyor olabilir bir tırnak içinde ve bir boşluk, "gerçek" devam ederken içeride tespit edildiğinde zaman, başka <> = <|>
Israr Han

-6

Evet, dize nesnesi, Split()aranacak karakteri bir sınırlayıcı olarak belirten tek bir parametre alan ve içinde tek tek değerlerle bir dizi dizesi (dize []) döndüren yerleşik bir işleve sahiptir .


1
Bu src: "C: \ tmp \ Bazı Klasörler \ Alt Klasörler" bölümünü hatalı şekilde böler.
Anton

Boşluklara ayırmayı geçici olarak kapatan dize içindeki tırnaklara ne dersiniz?
Daniel Earwicker
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.