code

C #에서 명령 줄 인수 이스케이프

codestyles 2020. 10. 27. 08:15
반응형

C #에서 명령 줄 인수 이스케이프


짧은 버전 :

그것은 따옴표로 인수를 마무리 탈출하기에 충분 \하고 "?

코드 버전

string[] argsProcessInfo.Arguments를 사용하여 명령 줄 인수 를 다른 프로세스 에 전달하고 싶습니다 .

ProcessStartInfo info = new ProcessStartInfo();
info.FileName = Application.ExecutablePath;
info.UseShellExecute = true;
info.Verb = "runas"; // Provides Run as Administrator
info.Arguments = EscapeCommandLineArguments(args);
Process.Start(info);

문제는 인수를 배열로 가져 와서 단일 문자열로 병합해야한다는 것입니다. 내 프로그램을 속이기 위해 인수를 만들 수 있습니다.

my.exe "C:\Documents and Settings\MyPath \" --kill-all-humans \" except fry"

이 답변 에 따르면 단일 인수를 이스케이프하기 위해 다음 함수를 만들었지 만 무언가를 놓쳤을 수도 있습니다.

private static string EscapeCommandLineArguments(string[] args)
{
    string arguments = "";
    foreach (string arg in args)
    {
        arguments += " \"" +
            arg.Replace ("\\", "\\\\").Replace("\"", "\\\"") +
            "\"";
    }
    return arguments;
}

이것으로 충분합니까 아니면 이것에 대한 프레임 워크 기능이 있습니까?


그래도 그것보다 더 복잡합니다!

나는 관련 문제 (모든 매개 변수 + 일부 추가 매개 변수를 사용하여 백 엔드를 호출하는 프런트 엔드 .exe 작성)가 있었으므로 사람들이 어떻게하는지 살펴보고 귀하의 질문에 부딪 혔습니다. 처음에는 모든 것이 당신이 제안한대로 잘하는 것 같았습니다 arg.Replace (@"\", @"\\").Replace(quote, @"\"+quote).

그러나 내가 arguments를 호출 할 때 c:\temp a\\b, 이것은 c:\tempand로 전달되어 a\\b백엔드가 호출되는 결과를 가져옵니다 "c:\\temp" "a\\\\b".-이것은 잘못된 것입니다. 왜냐하면 그것은 두 개의 인수가 c:\\temp있고 a\\\\b-우리가 원하는 것이 아니기 때문입니다 ! 우리는 탈출에 지나치게 열광했습니다 (창은 유닉스가 아닙니다!).

그래서 나는 자세히 http://msdn.microsoft.com/en-us/library/system.environment.getcommandlineargs.aspx를 읽고 실제로 그러한 경우가 처리되는 방법을 설명합니다. 백 슬래시는 이중 앞에서 이스케이프 처리됩니다. 인용문.

다중 \이 처리 되는 방식에 비틀림이 있으며 설명은 잠시 동안 현기증을 남길 수 있습니다. 여기서 말한 이스케이프 제거 규칙을 다시 말하겠습니다 . N 의 하위 문자열이 \있고 뒤에 ". 이스케이프 취소 할 때, 우리는 그 문자열로 대체 INT (N / 2) \ 과 IFF에 N이 홀수이고, 우리는 추가 "끝에.

이러한 디코딩을위한 인코딩은 다음과 같습니다. 인수의 경우 0 개 이상의 각 하위 문자열을 찾은 \다음 "이를 두 번 다수로 교체 한 \다음 \". 우리는 그렇게 할 수 있습니다 :

s = Regex.Replace(arg, @"(\\*)" + "\"", @"$1$1\" + "\"");

그게 다야...

추신. ... 아니 . 잠깐만 요-더 있습니다! :)

인코딩을 올바르게 수행했지만 모든 매개 변수를 큰 따옴표로 묶기 때문에 문제가 있습니다 (일부 공백이있는 경우). 경계 문제가 있습니다. 매개 변수가로 끝나는 경우 뒤에 \추가하면 "닫는 따옴표의 의미가 깨집니다. c:\one\ two를 구문 분석 c:\one\하고 two다음에 재 조립 될 것입니다 "c:\one\" "two"그 것이다 나 (오)은 하나 개의 인수로 이해 c:\one" two(나는, 내가 그것을 만드는 아니라고 시도). 따라서 추가로 필요한 것은 인수가 끝나는 지 확인 \하고 만약 그렇다면 다음과 같이 끝에 백 슬래시 수를 두 배로 늘리는 것입니다.

s = "\"" + Regex.Replace(s, @"(\\+)$", @"$1$1") + "\"";

내 대답은 Nas Banov의 대답과 비슷했지만 필요한 경우에만 큰 따옴표를 원했습니다 .

불필요한 큰 따옴표 잘라 내기

내 코드는 매개 변수의 문자 제한에 가까워 질 때 중요한 * 중요한 큰 따옴표 를 항상 주위에 불필요하게 두는 것을 저장 합니다 .

/// <summary>
/// Encodes an argument for passing into a program
/// </summary>
/// <param name="original">The value that should be received by the program</param>
/// <returns>The value which needs to be passed to the program for the original value 
/// to come through</returns>
public static string EncodeParameterArgument(string original)
{
    if( string.IsNullOrEmpty(original))
        return original;
    string value = Regex.Replace(original, @"(\\*)" + "\"", @"$1\$0");
    value = Regex.Replace(value, @"^(.*\s.*?)(\\*)$", "\"$1$2$2\"");
    return value;
}

// This is an EDIT
// Note that this version does the same but handles new lines in the arugments
public static string EncodeParameterArgumentMultiLine(string original)
{
    if (string.IsNullOrEmpty(original))
        return original;
    string value = Regex.Replace(original, @"(\\*)" + "\"", @"$1\$0");
    value = Regex.Replace(value, @"^(.*\s.*?)(\\*)$", "\"$1$2$2\"", RegexOptions.Singleline);

    return value;
}

설명

백 슬래시큰 따옴표를 올바르게 이스케이프하려면 여러 개의 백 슬래시작은 큰 따옴표다음으로 대체하면 됩니다.

string value = Regex.Replace(original, @"(\\*)" + "\"", @"\$1$0");

원래 백 슬래시 + 1과 원래 큰 따옴표의 두 배 추가 . 즉, '\'+ originalbackslash + originalbackslashes + ' "'. $ 0에는 원래의 백 슬래시 와 원래의 큰 따옴표가 있기 때문에 $ 1 $ 0을 사용 했습니다.

value = Regex.Replace(value, @"^(.*\s.*?)(\\*)$", "\"$1$2$2\"");

이것은 공백을 포함하는 전체 줄과 만 일치 할 수 있습니다.

일치 하면 시작과 끝에 큰 따옴표추가합니다 .

인수의 끝에 원래 백 슬래시 가 있었다면 인용되지 않았을 것입니다. 이제 끝에 큰 따옴표 가 있어야합니다. 따라서 중복되어 모두 따옴표를 붙이고 의도하지 않게 최종 큰 따옴표를 인용하는 것을 방지합니다.

첫 번째 섹션에 대해 최소 일치를 수행하므로 마지막. *? 마지막 백 슬래시와 일치하지 않습니다.

산출

따라서 이러한 입력은 다음 출력을 생성합니다.

여보세요

여보세요

\ 안녕하세요 \ 12 \ 3 \

\ 안녕하세요 \ 12 \ 3 \

안녕하세요 세계

"안녕하세요"

\"여보세요\"

\\"여보세요\\\"

\ "안녕 \\ 세계

"\\"안녕 \ 세계 "

\ "안녕하세요 \\\ 세상 \

"\\"안녕하세요 \\\ 세계 \\ "

안녕 세상 \\

"안녕하세요. \\\\"


나는 이것에도 문제가 있었다. args를 파싱하는 대신 원본 명령 줄 전체를 가져와 실행 파일을 잘라 냈습니다. 이것은 필요 / 사용되지 않더라도 호출에서 공백을 유지하는 추가적인 이점이있었습니다. 여전히 실행 파일에서 이스케이프를 추적해야하지만 args보다 쉬웠습니다.

var commandLine = Environment.CommandLine;
var argumentsString = "";

if(args.Length > 0)
{
    // Re-escaping args to be the exact same as they were passed is hard and misses whitespace.
    // Use the original command line and trim off the executable to get the args.
    var argIndex = -1;
    if(commandLine[0] == '"')
    {
        //Double-quotes mean we need to dig to find the closing double-quote.
        var backslashPending = false;
        var secondDoublequoteIndex = -1;
        for(var i = 1; i < commandLine.Length; i++)
        {
            if(backslashPending)
            {
                backslashPending = false;
                continue;
            }
            if(commandLine[i] == '\\')
            {
                backslashPending = true;
                continue;
            }
            if(commandLine[i] == '"')
            {
                secondDoublequoteIndex = i + 1;
                break;
            }
        }
        argIndex = secondDoublequoteIndex;
    }
    else
    {
        // No double-quotes, so args begin after first whitespace.
        argIndex = commandLine.IndexOf(" ", System.StringComparison.Ordinal);
    }
    if(argIndex != -1)
    {
        argumentsString = commandLine.Substring(argIndex + 1);
    }
}

Console.WriteLine("argumentsString: " + argumentsString);

Everyone quotes 명령 줄 인수의 C ++ 함수를 잘못된 방식으로 이식했습니다 .

잘 작동하지만 cmd.exe명령 줄을 다르게 해석 한다는 점에 유의해야합니다 . 만약 당신의 명령 줄이 당신의 명령 줄을 해석 할 것이라면 ( 그리고 만약 에 언급 된 기사의 원저자처럼) cmd.exe쉘 메타 문자도 이스케이프해야합니다.

/// <summary>
///     This routine appends the given argument to a command line such that
///     CommandLineToArgvW will return the argument string unchanged. Arguments
///     in a command line should be separated by spaces; this function does
///     not add these spaces.
/// </summary>
/// <param name="argument">Supplies the argument to encode.</param>
/// <param name="force">
///     Supplies an indication of whether we should quote the argument even if it 
///     does not contain any characters that would ordinarily require quoting.
/// </param>
private static string EncodeParameterArgument(string argument, bool force = false)
{
    if (argument == null) throw new ArgumentNullException(nameof(argument));

    // Unless we're told otherwise, don't quote unless we actually
    // need to do so --- hopefully avoid problems if programs won't
    // parse quotes properly
    if (force == false
        && argument.Length > 0
        && argument.IndexOfAny(" \t\n\v\"".ToCharArray()) == -1)
    {
        return argument;
    }

    var quoted = new StringBuilder();
    quoted.Append('"');

    var numberBackslashes = 0;

    foreach (var chr in argument)
    {
        switch (chr)
        {
            case '\\':
                numberBackslashes++;
                continue;
            case '"':
                // Escape all backslashes and the following
                // double quotation mark.
                quoted.Append('\\', numberBackslashes*2 + 1);
                quoted.Append(chr);
                break;
            default:
                // Backslashes aren't special here.
                quoted.Append('\\', numberBackslashes);
                quoted.Append(chr);
                break;
        }
        numberBackslashes = 0;
    }

    // Escape all backslashes, but let the terminating
    // double quotation mark we add below be interpreted
    // as a metacharacter.
    quoted.Append('\\', numberBackslashes*2);
    quoted.Append('"');

    return quoted.ToString();
}

명령 줄 인코딩 / 이스케이프와 관련된 대부분의 문제를 처리하는 작은 프로젝트를 GitHub에 게시했습니다.

https://github.com/ericpopivker/Command-Line-Encoder

있습니다 CommandLineEncoder.Utils.cs의 클래스뿐만 아니라 인코딩 / 디코딩 기능을 확인하는 단위 테스트는.


명령 줄에서 이스케이프 문자를 사용하는 방법을 보여주기 위해 작은 샘플을 작성했습니다.

public static string BuildCommandLineArgs(List<string> argsList)
{
    System.Text.StringBuilder sb = new System.Text.StringBuilder();

    foreach (string arg in argsList)
    {
        sb.Append("\"\"" + arg.Replace("\"", @"\" + "\"") + "\"\" ");
    }

    if (sb.Length > 0)
    {
        sb = sb.Remove(sb.Length - 1, 1);
    }

    return sb.ToString();
}

다음은 테스트 방법입니다.

    List<string> myArgs = new List<string>();
    myArgs.Add("test\"123"); // test"123
    myArgs.Add("test\"\"123\"\"234"); // test""123""234
    myArgs.Add("test123\"\"\"234"); // test123"""234

    string cmargs = BuildCommandLineArgs(myArgs);

    // result: ""test\"123"" ""test\"\"123\"\"234"" ""test123\"\"\"234""

    // when you pass this result to your app, you will get this args list:
    // test"123
    // test""123""234
    // test123"""234

요점은 각 arg를 큰 따옴표 ( ""arg "")로 감싸고 arg 값 안의 모든 따옴표를 이스케이프 된 따옴표 (test \ "123)로 바꾸는 것입니다.


static string BuildCommandLineFromArgs(params string[] args)
{
    if (args == null)
        return null;
    string result = "";

    if (Environment.OSVersion.Platform == PlatformID.Unix 
        || 
        Environment.OSVersion.Platform == PlatformID.MacOSX)
    {
        foreach (string arg in args)
        {
            result += (result.Length > 0 ? " " : "") 
                + arg
                    .Replace(@" ", @"\ ")
                    .Replace("\t", "\\\t")
                    .Replace(@"\", @"\\")
                    .Replace(@"""", @"\""")
                    .Replace(@"<", @"\<")
                    .Replace(@">", @"\>")
                    .Replace(@"|", @"\|")
                    .Replace(@"@", @"\@")
                    .Replace(@"&", @"\&");
        }
    }
    else //Windows family
    {
        bool enclosedInApo, wasApo;
        string subResult;
        foreach (string arg in args)
        {
            enclosedInApo = arg.LastIndexOfAny(
                new char[] { ' ', '\t', '|', '@', '^', '<', '>', '&'}) >= 0;
            wasApo = enclosedInApo;
            subResult = "";
            for (int i = arg.Length - 1; i >= 0; i--)
            {
                switch (arg[i])
                {
                    case '"':
                        subResult = @"\""" + subResult;
                        wasApo = true;
                        break;
                    case '\\':
                        subResult = (wasApo ? @"\\" : @"\") + subResult;
                        break;
                    default:
                        subResult = arg[i] + subResult;
                        wasApo = false;
                        break;
                }
            }
            result += (result.Length > 0 ? " " : "") 
                + (enclosedInApo ? "\"" + subResult + "\"" : subResult);
        }
    }

    return result;
}

인수를 추가하는 것은 훌륭하지만 탈출하지는 않습니다. 이스케이프 시퀀스가 ​​이동해야하는 메서드에 주석을 추가했습니다.

public static string ApplicationArguments()
{
    List<string> args = Environment.GetCommandLineArgs().ToList();
    args.RemoveAt(0); // remove executable
    StringBuilder sb = new StringBuilder();
    foreach (string s in args)
    {
        // todo: add escape double quotes here
        sb.Append(string.Format("\"{0}\" ", s)); // wrap all args in quotes
    }
    return sb.ToString().Trim();
}

대체 접근법

If you're passing a complex object such as nested JSON and you have control over the system that's receiving the command line arguments, it's far easier to just encode the command line arg/s as base64 and then decode them from the receiving system.

See here: Encode/Decode String to/from Base64

Use Case: I needed to pass a JSON object that contained an XML string in one of the properties which was overly complicated to escape. This solved it.

참고URL : https://stackoverflow.com/questions/5510343/escape-command-line-arguments-in-c-sharp

반응형