我可以以原始形式(例如包括引号)获得我的申请的论据吗?

时间:2013-05-09 02:58:59

标签: c# .net console-application command-line-arguments processstartinfo

我有一个.Net应用程序,它接受一堆命令行参数,处理其中一些,并将其余部分用作另一个应用程序的参数

E.g。

MyApp.exe foo1 App2.exe arg1 arg2 ...

MyApp.exe是我的申请, foo1是我的应用程序关注的参数。 App2.exe是另一个应用程序,我的应用程序将运行带有arg1 arg2等的App2作为参数。

目前我的应用程序只是使用类似这样的

运行App2.exe

Process.Start(args[1], String.Join(" ", args.Skip(2))。因此上面的命令将正确运行:带有参数“arg1 arg2”的App2.exe。但是,请考虑这样的事情

MyApp.exe foo1 notepad.exe "C:\Program Files\readme.txt"

上面的代码不会知道引号,并且会运行带有参数C:\ Program Files \ readme.txt(不含引号)的notepad.exe。 我该如何解决这个问题?

9 个答案:

答案 0 :(得分:6)

Environment.CommandLine

会给你一个确切的命令行 - 你必须解析你的应用程序的路径,否则就像一个魅力 - @idle_mind提到这个早期(有点)

编辑将示例移到答案中(因为人们显然仍然在寻找这个答案)。注意,当debuging vshost稍微搞砸命令行时。

#if DEBUG             
    int bodgeLen = "\"vshost.\"".Length; 
#else             
    int bodgeLen = "\"\"".Length; 
#endif              
string a = Environment.CommandLine.Substring(Assembly.GetExecutingAssembly().Location.Lengt‌​h+bodgeLen).Trim();

答案 1 :(得分:4)

您需要修改MyApp以包含带引号的任何参数。

简短的故事,新代码应该是这样的:

var argsToPass = args.Skip(2).Select(o => "\"" + o.Replace("\"", "\\\"") + "\"");
Process.Start(args[1], String.Join(" ", argsToPass);

逻辑是:

  1. 每个参数都应该用引号括起来,所以如果你打电话给:

    MyApp.exe foo1 notepad.exe "C:\Program Files\readme.txt"

    该应用程序将以这种方式调用:

    notepad.exe "C:\Program Files\readme.txt"

  2. 每个参数都应该转义引号(如果有的话),所以如果你打电话给:

    MyApp.exe foo1 notepad.exe "C:\Program Files\Some Path with \"Quote\" here\readme.txt"

    该应用程序将以这种方式调用:

    notepad.exe "C:\Program Files\Some Path with \"Quote\" here\readme.txt"

答案 2 :(得分:2)

使用Environment.GetCommandLine(),因为它会将参数保持在引号中作为一个参数。

答案 3 :(得分:1)

嗯,简单的答案是在调用MyApp2.exe时将每个参数都包装在引号中。 包装一个单词的参数并没有什么坏处,它将修复它在参数中有空格的情况。

唯一可能出错的是,如果参数已经有一个转义引号。

答案 4 :(得分:1)

您可以使用反斜杠作为转义引号。以下将工作

MyApp.exe foo1 notepad.exe \"C:\Program Files\readme.txt\"

如果您不知道将要运行哪些其他exes以及他们期望的参数是什么,那么上面将是最佳解决方案。在这种情况下,您无法在程序中添加引号。

提供在运行应用程序时有引号时添加反斜杠的说明

答案 5 :(得分:1)

感谢@ mp3ferret有正确的想法。但是没有使用Environment.CommandLine的解决方案的示例,所以我继续创建了一个OriginalCommandLine类,它将获得最初输入的命令行参数。

参数在tokenizer正则表达式中定义为任何类型字符的双引号字符串,或非空格字符的未加引号字符串。在引用的字符串中,引号字符可以通过反斜杠进行转义。 然而一个尾部反斜杠后跟一个双引号,然后空格不会被转义。

有理由我选择了转义的例外,因为空格是为了容纳以反斜杠结尾的引用路径。我相信你不太可能遇到你真正想要转义双重报价的情况。

代码

static public class OriginalCommandLine
{
    static Regex tokenizer = new Regex(@"""(?:\\""(?!\s)|[^""])*""|[^\s]+");
    static Regex unescaper = new Regex(@"\\("")(?!\s|$)");
    static Regex unquoter = new Regex(@"^\s*""|""\s*$");
    static Regex quoteTester = new Regex(@"^\s*""(?:\\""|[^""])*""\s*$");

    static public string[] Parse(string commandLine = null)
    {
        return tokenizer.Matches(commandLine ?? Environment.CommandLine).Cast<Match>()
            .Skip(1).Select(m => unescaper.Replace(m.Value, @"""")).ToArray();
    }

    static public string UnQuote(string text)
    {
        return (IsQuoted(text)) ? unquoter.Replace(text, "") : text;
    }

    static public bool IsQuoted(string text)
    {
        return text != null && quoteTester.Match(text).Success;
    }
}

结果

正如您从下面的结果中看到的,上述方法修复程序维护引号,同时更优雅地处理您可能遇到的实际情况。

Test:
    ConsoleApp1.exe foo1 notepad.exe "C:\Progra\"m Files\MyDocuments\"  "C:\Program Files\bar.txt"

    args[]:
[0]: foo1
[1]: notepad.exe
[2]: C:\Progra"m Files\MyDocuments"  C:\Program
[3]: Files\bar.txt

    CommandLine.Parse():
[0]: foo1
[1]: notepad.exe
[2]: "C:\Progra"m Files\MyDocuments\"
[3]: "C:\Program Files\bar.txt"

最后

我讨论了使用替代方案来转义双引号。我觉得使用""更好,因为命令行经常处理反斜杠。我保留了反斜杠转义方法,因为它向后兼容命令行参数的正常处理方式。

如果要使用该方案,请对正则表达式进行以下更改:

static Regex tokenizer = new Regex(@"""(?:""""|[^""])*""|[^\s]+");
static Regex unescaper = new Regex(@"""""");
static Regex unquoter = new Regex(@"^\s*""|""\s*$");
static Regex quoteTester = new Regex(@"^\s*""(?:""""|[^""])*""\s*$");

如果您希望更接近args的预期,但引号保持不变,请更改两个正则表达式。仍然存在细微差别,"abc"d将从abcd返回args,但从我的解决方案返回[0] = "abc", [1] = d

static Regex tokenizer = new Regex(@"""(?:\\""|[^""])*""|[^\s]+");
static Regex unescaper = new Regex(@"\\("")");

如果真的确实希望获得与args相同数量的元素,请使用以下内容:

static Regex tokenizer = new Regex(@"(?:[^\s""]*""(?:\\""|[^""])*"")+|[^\s]+");
static Regex unescaper = new Regex(@"\\("")");

完全匹配的结果

Test: "zzz"zz"Zzz" asdasd zz"zzz" "zzz"

args               OriginalCommandLine
-------------      -------------------
[0]: zzzzzZzz      [0]: "zzz"zz"Zzz"
[1]: asdasd        [1]: asdasd
[2]: zzzzz         [2]: zz"zzz"
[3]: zzz           [3]: "zzz"

答案 6 :(得分:0)

尝试以下方法。

此代码保留了双引号字符,并提供了转义\和“字符”的选项(请参阅下面代码中的注释)。

static void Main(string[] args)
{
    // This project should be compiled with "unsafe" flag!
    Console.WriteLine(GetRawCommandLine());
    var prms = GetRawArguments();
    foreach (var prm in prms)
    {
        Console.WriteLine(prm);
    }
}

[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
private static extern System.IntPtr GetCommandLine();
public static string GetRawCommandLine()
{
    // Win32 API
    string s = Marshal.PtrToStringAuto(GetCommandLine());

    // or better, managed code as suggested by @mp3ferret
    // string s = Environment.CommandLine;
    return s.Substring(s.IndexOf('"', 1) + 1).Trim();
}

public static string[] GetRawArguments()
{
    string cmdline = GetRawCommandLine();

    // Now let's split the arguments. 
    // Lets assume the fllowing possible escape sequence:
    // \" = "
    // \\ = \
    // \ with any other character will be treated as \
    //
    // You may choose other rules and implement them!

    var args = new ArrayList();
    bool inQuote = false;
    int pos = 0;
    StringBuilder currArg = new StringBuilder();
    while (pos < cmdline.Length)
    {
        char currChar = cmdline[pos];

        if (currChar == '"')
        {
            currArg.Append(currChar);
            inQuote = !inQuote;
        }
        else if (currChar == '\\')
        {
            char nextChar = pos < cmdline.Length - 1 ? cmdline[pos + 1] : '\0';
            if (nextChar == '\\' || nextChar == '"')
            {
                currArg.Append(nextChar);
                pos += 2;
                continue;
            }
            else
            {
                currArg.Append(currChar);
            }
        }
        else if (inQuote || !char.IsWhiteSpace(currChar))
        {
            currArg.Append(currChar);
        }
        if (!inQuote && char.IsWhiteSpace(currChar) && currArg.Length > 0)
        {
            args.Add(currArg.ToString());
            currArg.Clear();
        }
        pos++;
    }

    if (currArg.Length > 0)
    {
        args.Add(currArg.ToString());
        currArg.Clear();
    }
    return (string[])args.ToArray(typeof(string));
}

答案 7 :(得分:0)

尝试使用“\”“。​​我必须传递url作为参数,这就是:

_filenameDestin和_zip是网址。我希望它有所帮助。

string ph = "\"";
var psi = new ProcessStartInfo();
psi.Arguments = "a -r " + ph + _filenameDestin + ".zip " + ph + _filenameDestin + ph;
psi.FileName = _zip;
var p = new Process();
p.StartInfo = psi;
p.Start();
p.WaitForExit();

答案 8 :(得分:0)

一种解决方案可能是尝试使用免费的第三方工具Command Line Parser来设置应用程序以获取特定标记。

例如,您可以按如下方式定义接受的选项:

    internal sealed class Options
    {
        [Option('a', "mainArguments", Required=true, HelpText="The arguments for the main application")]
        public String MainArguments { get; set; }

        [Option('t', "targetApplication", Required = true, HelpText = "The second application to run.")]
        public String TargetApplication { get; set; }

        [Option('p', "targetParameters", Required = true, HelpText = "The arguments to pass to the target application.")]
        public String targetParameters { get; set; }

        [ParserState]
        public IParserState LastParserState { get; set; }


        [HelpOption]
        public string GetUsage()
        {
            return HelpText.AutoBuild(this, current => HelpText.DefaultParsingErrorsHandler(this, current));
        }
    }

然后可以在Program.cs中使用,如下所示:

    static void Main(string[] args)
    {
        Options options = new Options();
        var parser = new CommandLine.Parser();

        if (parser.ParseArgumentsStrict(args, options, () => Environment.Exit(-2)))
        {
            Run(options);
        }
    }


private static void Run(Options options)
{
    String mainArguments = options.MainArguments;
    // Do whatever you want with your main arguments.

    String quotedTargetParameters = String.Format("\"{0}\"", options.TargetParameters);   
    Process targetProcess = Process.Start(options.TargetApplication, quotedTargetParameters);
}

然后你可以在命令行上调用它:

myApp -a mainArgs -t targetApp -p "target app parameters"

这可以避免尝试弄清楚哪个应用程序的参数是什么,同时也允许用户以他们想要的任何顺序指定它们。如果你决定在路上添加另一个参数,你可以轻松地做到这一点而不会破坏一切。

编辑:更新了运行方法,以包括在目标参数周围添加引号的功能。