コマンドラインパラメーターを含む文字列をC#のstring []に分割する


88

別の実行可能ファイルに渡されるコマンドラインパラメーターを含む単一の文字列があり、コマンドラインでコマンドが指定された場合の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 2008年

1
同じことを探して良い質問です。「hey .netがここを公開しています...」という人を見つけたいと思っていました。:)ある時点でそれを見つけた場合は、ここで投稿します。まだ有効な質問です!
MikeJansen 14

この機能も必要なので、以下の回答で純粋に管理されたバージョンを作成しました。
ygoe 2014年

回答:


74

Earwickerによる優れた純粋なマネージドソリューションに加えて、完全を期すために、Windows は文字列を文字列の配列に分割する関数も提供していることに言及する価値があります。CommandLineToArgvW

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

標準のCランタイムargvおよびargc値と同様の方法で、Unicodeコマンドライン文字列を解析し、コマンドライン引数へのポインターの配列を、そのような引数の数とともに返します。

このAPIをC#から呼び出して、結果の文字列配列をマネージコードでアンパックする例は、「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 \\」である必要があります。
マグナスリンデ09

8
CommandLineArgvWが最初の引数がプログラム名であることを想定していることも注目に値します。渡されない場合、適用される解析マジックはまったく同じではありません。次のようなものでそれを偽造できます:CommandLineToArgs("foo.exe " + commandLine).Skip(1).ToArray();
Scott Wegner

4
完全を期すために、MSVCRTはCommandLineToArgvW()を使用してコマンドラインをargc / argvに変換しません。異なる独自のコードを使用します。たとえば、次の文字列でCreateProcessを呼び出してみてください:a "b c" def。)(メインでは、(MSDNに記載されているように)3つの引数を取得したいが、CommandLineToArgvW()/ GetCommandLineW()コンボはあなたに2を与えるだろう
LRN

7
これはめちゃくちゃです。典型的なMSスープ。何も正規化されておらず、MSの世界でKISSが尊重されることは決してありません。
v.oddou 2015

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メソッドよりも手間がかかります。両端から1文字だけをトリミングし、片端だけをトリミングすることはありません。

    public static string TrimMatchingQuotes(this string input, char quote)
    {
        if ((input.Length >= 2) && 
            (input[0] == quote) && (input[input.Length - 1] == quote))
            return input.Substring(1, input.Length - 2);

        return input;
    }

また、いくつかのテストも必要になると思います。さて、それでは大丈夫です。しかし、これは絶対に最後のものでなければなりません!最初に、分割の結果を予想される配列の内容と比較するヘルパー関数:

    public static void Test(string cmdLine, params string[] args)
    {
        string[] split = SplitCommandLine(cmdLine).ToArray();

        Debug.Assert(split.Length == args.Length);

        for (int n = 0; n < split.Length; n++)
            Debug.Assert(split[n] == args[n]);
    }

それから私はこのようなテストを書くことができます:

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

要件のテストは次のとおりです。

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

実装には、それが理にかなっている場合に引数の前後の引用符を削除するという追加の機能があることに注意してください(TrimMatchingQuotes関数のおかげです)。これは通常のコマンドライン解釈の一部だと思います。


私は正しい期待される出力を持っていなかったので、これを答えとしてマークを外さなければなりませんでした。実際の出力では、最終的な配列に「」が含まれていてはいけません
Anton

16
私は常に変化する要件から逃れるためにStack Overflowに来ています!:) TrimMatchingQuotes()の代わりにReplace( "\" "、" ")を使用してすべての引用符を取り除くことができますが、Windowsでは\"がサポートされているため、引用符文字を渡すことができます。私のSplit関数はそれを行うことができません。
Daniel Earwicker、2008年

1
素敵な1つのEarwicker :)アントン:これは私が以前の投稿であなたに説明しようとしていた解決策ですが、Earwickerはそれを書くのにはるかに優れた仕事をしました;)また、それを大幅に拡張しました;)
Israr Khan

コマンドライン引数の区切り文字は空白だけではありませんか?
Louis Rhys、2009

@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)を使用しました。動作しているようです。
アントン

3
私はWindowsがパラメーターの引用符をエスケープする方法を持っている必要があると思います...このアルゴリズムはそれを考慮に入れていません。
rmeador 2008年

空白行の削除、外側の引用符の削除、およびエスケープされた引用符の処理は、読者にとってはexcersizeとして残されます。
Jeffrey L Whitledge、2008年

Char.IsWhiteSpace()はここで役立ちます
Sam Mackrill

このソリューションは、引数が単一のスペースで区切られている場合に適していますが、引数が複数のスペースで区切られている場合は失敗します。正しいソリューションへのリンク:stackoverflow.com/a/59131568/3926504
Dilip Nannaware

13

私はジェフリー・L・ホイットレッジからの回答を受け取り、少し強化しました。

現在は一重引用符と二重引用符の両方をサポートしています。他の型付き引用符を使用して、パラメーター自体に引用符を使用できます。

これらは引数情報に寄与しないため、引数から引用符も取り除きます。

    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

良いと純粋な管理ソリューションによってエリカーは、このような引数を処理するために失敗しました:

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

それは3つの要素を返しました:

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

したがって、「引用された\ "エスケープ\"引用」をサポートするための修正は次のとおりです。

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するaAAAb"ASDS"cdSADSDかというような結果を依然として見逃していますaAAA"b"ASDS"c"dSADSD。をに変更しTrimMatchingQuotesて、次のようにRegex("(?<!\\\\)\\\"")使用すること を検討します。
Scis

4

2
便利です-しかし、これは現在のプロセスに送信されたコマンドライン引数のみを取得します。要件は、「コマンドがコマンドラインで指定されている場合に C#が行うのと同じ方法で」文字列からstring []を取得することでした。デ
コンパイラーを

Jon Gallowayも発見したように(weblogs.asp.net/jgalloway/archive/2006/09/13/…)、逆コンパイラはAtifの答え(stackoverflow.com/questions/298830/…)に戻るのに役立ちません。
rohancragg 2011年

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

これは、.NET Frameworkから.NET Coreに移植されたコードで、MSVC Cライブラリまたは CommandLineToArgvW

これが正規表現を使っていくつかの不正行為を処理し、引数のゼロビットを無視する中途半端な試みです。それは少し奇妙です。

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[]だけが必要です。
アントン

.NETを反映しても、どこにも行きませんでした。見るとモノラル のソース コードは、 提案され、この引数の分割はCLRによって行われていないことではなく、すでにオペレーティングシステムから来ています。Cメイン関数のargc、argvパラメーターについて考えてみます。したがって、OS API以外に再利用するものはありません。
ygoe 2014年

1

純粋な管理ソリューション役に立つかもしれません。WINAPI関数の「問題」コメントが多すぎて、他のプラットフォームでは使用できません。これは、明確に定義された動作(必要に応じて変更できる)の私のコードです。

.NET / Windowsがそのstring[] argsパラメーターを提供するときに行うのと同じことを行う必要があり、私はそれをいくつかの「興味深い」値と比較しました。

これは、入力文字列から各単一文字を取得して現在の状態について解釈し、出力と新しい状態を生成する、古典的な状態マシンの実装です。状態変数で定義されescapeinQuotehadQuote及びprevCh、出力はに集められcurrentArgそしてargs

実際のコマンドプロンプト(Windows 7)での実験によって発見したいくつかの特産品:引用された範囲内で\\生成\\"生成"""生成"

^それを倍増ないとき、それは常に消える:キャラクターがあまりにも、魔法のようです。それ以外の場合は、実際のコマンドラインには影響しません。私の実装はこれをサポートしていません。この振る舞いのパターンを見つけられなかったからです。多分誰かがそれについてもっと知っています。

このパターンに当てはまらないものは、次のコマンドです。

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

cmdコマンドは、外側の引用符をキャッチし、そのまま休息を取るように見えます。これには特別な魔法のソースが必要です。

私の方法ではベンチマークを行っていませんが、かなり高速だと考えています。これは使用Regexせず、文字列連結も行いませんが、代わりにa StringBuilderを使用して引数の文字を収集し、それらをリストに入れます。

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

1

使用する:

public static string[] SplitArguments(string args) {
    char[] parmChars = args.ToCharArray();
    bool inSingleQuote = false;
    bool inDoubleQuote = false;
    bool escaped = false;
    bool lastSplitted = false;
    bool justSplitted = false;
    bool lastQuoted = false;
    bool justQuoted = false;

    int i, j;

    for(i=0, j=0; i<parmChars.Length; i++, j++) {
        parmChars[j] = parmChars[i];

        if(!escaped) {
            if(parmChars[i] == '^') {
                escaped = true;
                j--;
            } else if(parmChars[i] == '"' && !inSingleQuote) {
                inDoubleQuote = !inDoubleQuote;
                parmChars[j] = '\n';
                justSplitted = true;
                justQuoted = true;
            } else if(parmChars[i] == '\'' && !inDoubleQuote) {
                inSingleQuote = !inSingleQuote;
                parmChars[j] = '\n';
                justSplitted = true;
                justQuoted = true;
            } else if(!inSingleQuote && !inDoubleQuote && parmChars[i] == ' ') {
                parmChars[j] = '\n';
                justSplitted = true;
            }

            if(justSplitted && lastSplitted && (!lastQuoted || !justQuoted))
                j--;

            lastSplitted = justSplitted;
            justSplitted = false;

            lastQuoted = justQuoted;
            justQuoted = false;
        } else {
            escaped = false;
        }
    }

    if(lastQuoted)
        j--;

    return (new string(parmChars, 0, j)).Split(new[] { '\n' });
}

路地の答えの蒸気に基づいて、これは^エスケープもサポートします。

例:

  • これはテストです
    • この
    • です
    • a
    • テスト
  • これはテストです
    • この
    • です
    • テスト
  • これは^ "は^"テストです
    • この
    • 「です
    • a」
    • テスト
  • この "" "は^^テストです"
    • この
    • ^テストです

複数のスペースもサポートします(スペースのブロックごとに1回だけ引数を分割します)。


3つのうちの最後のものは、なんとかしてMarkdownに干渉し、意図したとおりにレンダリングされません。
Peter Mortensen、2018

ゼロ幅スペースで修正されました。
Fabio Iotti

0

現在、これは私が持っているコードです:

    private String[] SplitCommandLineArgument(String argumentString)
    {
        StringBuilder translatedArguments = new StringBuilder(argumentString);
        bool escaped = false;
        for (int i = 0; i < translatedArguments.Length; i++)
        {
            if (translatedArguments[i] == '"')
            {
                escaped = !escaped;
            }
            if (translatedArguments[i] == ' ' && !escaped)
            {
                translatedArguments[i] = '\n';
            }
        }

        string[] toReturn = translatedArguments.ToString().Split(new char[] { '\n' }, StringSplitOptions.RemoveEmptyEntries);
        for(int i = 0; i < toReturn.Length; i++)
        {
            toReturn[i] = RemoveMatchingQuotes(toReturn[i]);
        }
        return toReturn;
    }

    public static string RemoveMatchingQuotes(string stringToTrim)
    {
        int firstQuoteIndex = stringToTrim.IndexOf('"');
        int lastQuoteIndex = stringToTrim.LastIndexOf('"');
        while (firstQuoteIndex != lastQuoteIndex)
        {
            stringToTrim = stringToTrim.Remove(firstQuoteIndex, 1);
            stringToTrim = stringToTrim.Remove(lastQuoteIndex - 1, 1); //-1 because we've shifted the indicies left by one
            firstQuoteIndex = stringToTrim.IndexOf('"');
            lastQuoteIndex = stringToTrim.LastIndexOf('"');
        }
        return stringToTrim;
    }

エスケープされた引用符では機能しませんが、これまでに出くわしたケースでは機能します。


0

これはアントンのコードへの返答であり、エスケープされた引用符では機能しません。3箇所変更しました。

  1. コンストラクタのためのStringBuilderSplitCommandLineArguments、任意の交換\を」\ rを
  2. ではforループSplitCommandLineArguments、私は今置き換える\ r個のへの文字のバックを \」
  3. SplitCommandLineArgumentメソッドをprivateからpublic staticに変更しました。

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 []の単体テストを作成しますが、これをカバーするためにいくつかの統合テストを追加する可能性があります。
チャーリー・バーカー

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 2015

@EnamulHassanコードはポルトガル語でもあると思いposicao_ponteiro += ((fim - posicao_ponteiro)+1);ます。
MEMark 2018

0

これは、ジョブを実行する1つのライナーです(BurstCmdLineArgs(...)メソッド内のすべての作業を実行する1つのラインを参照してください)。

私が最も読みやすいコード行と呼ぶものではありませんが、読みやすくするために分割することができます。これは意図的に単純であり、すべての引数の場合(分割文字列の区切り文字を含むファイル名引数など)にはうまく機能しません。

このソリューションは、それを使用する私のソリューションでうまく機能しました。私が言ったように、それは可能なすべての引数フォーマットn-factorialを処理するためのラットの巣のコードなしで仕事を成し遂げる。

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

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

            string dummyString = string.Empty;

            var argDict = BurstCmdLineArgs(args);

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

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

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

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

            return argDict;
        }
    }
}

0

ここで気に入ったものが見つかりませんでした。小さなコマンドラインでスタックをめちゃくちゃにするのは嫌です(テラバイトのストリームの場合は別の話になります)。

これが私の見解です。次のような二重引用符を使用した引用エスケープをサポートしています。

param = "a 15" "画面は悪くない" 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');
}

-2

理解できたのかわかりませんが、スプリッターとして使われているキャラクターも本文中にあるのでしょうか?(それを除いて、それは二重の "?"でエスケープされます)

もしそうなら、私は作成します forループ <">が存在するすべてのインスタンスを<|>(または別の「安全な」文字)に置き換えますが、<">のみを置き換え、<"">は置き換えないようにしてください。

文字列を繰り返した後、以前に投稿したように文字列を分割しますが、今度は文字<|>で行います。


ダブル ""は@ @ .. "文字列リテラルであるため、ダブル" 's @ ".."文字列は通常の文字列の\エスケープされた "と同等です
Anton

「唯一の制限(私が信じる)は、スペースが「...」ブロック内で発生しない限り、文字列がスペースで区切られていることです->バズーカで鳥を撃っている可能性がありますが、ブール値を「true」引用符の内部で、「true」の間にスペースが検出された場合は続行し、それ以外の場合は<> = <|>
Israr Khan

-6

はい、文字列オブジェクトには、Split()検索する文字を区切り文字として指定する単一のパラメーターを取り、個々の値を含む文字列の配列(string [])を返す、という組み込み関数があります。


1
これにより、src: "C:\ tmp \ Some Folder \ Sub Folder"の部分が誤って分割されます。
アントン

スペースの分割を一時的にオフにする文字列内の引用符はどうですか?
Daniel Earwicker、2008年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.