【问题标题】:How to get the raw command line arguments如何获取原始命令行参数
【发布时间】:2013-01-04 02:42:59
【问题描述】:

我需要将命令行参数从 A.exe 传递给 B.exe。如果 A.exe 具有多参数,例如

A.exe -a="a" -b="b"'

我可以使用

BeginProcess("B.exe", **args!**)

启动 B.exe。 如何获取原始命令行参数,如

'-a="a" -b="b"'

【问题讨论】:

    标签: c++ c windows


    【解决方案1】:

    如果您使用的是 Windows,则使用 GetCommandLine 获取原始命令行。

    请注意,GetCommandLine 还包括 argv[0]。因此,在将 GetCommandLine 的输出传递给 B 之前,您必须超越 argv[0]。

    这是一些非错误检查代码

    #include <string.h>
    #include <windows.h>
    #include <iostream>
    #include <ctype.h>
    
    int main(int argc, char **argv)
    {
        LPTSTR cmd  = GetCommandLine();
    
        int l = strlen(argv[0]);
    
        if(cmd == strstr(cmd, argv[0]))
        {
            cmd = cmd + l;
            while(*cmd && isspace(*cmd))
                ++cmd;
        }
    
        std::cout<<"Command Line is : "<<cmd;
    
    }
    

    当我以A.exe -a="a" -b="b" 运行上述程序时,我得到以下输出

    A.exe -a="a" -b="b"
    Command Line is : -a="a" -b="b"
    

    【讨论】:

    • 为了完整起见,请注意,在这种特殊情况下,您不必分离出命令行的第一部分。 CreateProcess 函数允许您明确指定可执行文件,而不是作为命令行的一部分。
    • 不要使用此代码。如果可执行文件名称用引号引起来,它将失败(即使路径中没有空格也可能发生这种情况)。要获得正确的实现,请查看Wine's implementation of CommandLineToArgvW。 @harry-johnston:您仍然需要删除可执行路径才能将其替换为您要运行的程序,除非您正在生成自己的副本。
    • @benrg:不,你不知道,那是我的观点。如果通过lpApplicationName 参数显式指定可执行文件,则lpCommandLine 参数中指定的可执行文件将被忽略。
    • @harry-johnston: 来自内存,以及我对the CreateProcess documentation 的阅读,如果 lpApplicationName 不为 NULL,则 lpCommandLine 仅包含参数,因此旧的可执行文件名称将结束为 argv[1] 如果你不要删除它。
    • @benrg:不,命令行按原样传递给应用程序,因此您仍然需要为 argv[0] 指定应用程序名称。 (它不必是 actual 应用程序名称。)来自文档:“因为 argv[0] 是模块名称,C 程序员通常重复模块名称作为命令行。”我有依赖于这种行为的代码,所以我可以从个人经验中确认它是如何工作的。 :-)
    【解决方案2】:

    这是跳过可执行文件名称的唯一正确方法,基于Wine's implementation of CommandLineToArgvW

    char *s = lpCmdline;
    if (*s == '"') {
        ++s;
        while (*s)
            if (*s++ == '"')
                break;
    } else {
        while (*s && *s != ' ' && *s != '\t')
            ++s;
    }
    /* (optionally) skip spaces preceding the first argument */
    while (*s == ' ' || *s == '\t')
        s++;
    

    注意!当前的 Wine 实现,截至 2020 年 2 月 19 日 - git commit a10267172,现在从 dlls/shell32/shell32_main.c 移动到 dlls/shcore/main.c

    【讨论】:

      【解决方案3】:

      main的标准定义是

      int main(int argc, char* argv[])
      

      argv 变量包含命令行参数。 argc 变量表示使用了argv 数组中的条目数。

      【讨论】:

      • 是的,这是正确的。但我需要将 argv 加入单个字符串。
      • @miaodadao 如果您使用的是 C++,则可以使用 for 循环和 std::string 轻松完成。如果您使用的是 C,请查看 strcat() 函数。
      • 如果语法不是很简单,这种方法往往会破坏命令行。举个简单的例子,您在参数之间丢失了多个空格。
      • @HarryJohnston,你为什么会丢失多个空格? std::string(argv[1])+argv[2] 保留参数中的任何空格。您的意思是新流程无法再次可靠地分离参数吗?如果是这样,那正是我在 cmets 中对 PotatoSwatter 回答的批评......传递 argv 保留了单独的参数,连接和重新拆分不会。
      • @JonathanWakely:至少在 Microsoft C 中,空格是从 argv[] 中删除的,因此连接 argv[1] 和 argv[2] 会丢失最初在参数 1 和参数 2 之间的任何空格。 GetCommandLine 完全按照最初传递的方式保留空白。 (当然,大多数情况可以使用 argv[1] + " " + argv[2] 因为新进程可能会丢弃任何额外的空格。但也有边缘情况.)
      【解决方案4】:

      在您的程序开始运行之前,输入到 shell 的原始字符串由 shell 转换为 argv。除了argv之外,我从未听说过提供“原始”命令行的操作系统或shell。

      如果用户使用引号将空格字符传递到您的参数中怎么办?如果他们使用反斜杠来转义引号内的引号怎么办?不同的shell甚至可能有不同的引用规则。

      如果您有一个类似argv 的列表,您应该尝试找到一个接受它的 API,而不是尝试实现仅辅助实际目标的字符串处理。 Microsoft 非常重视安全性,他们当然提供了一些不需要为您的应用程序添加安全漏洞的东西。

      我找不到关于任何 C/C++ API BeginProcess 的文档;我有点假设这是 Windows,但无论如何您都应该仔细检查您平台的参考手册以获取替代系统调用。

      【讨论】:

      • 除了argv之外,我从未听说过有操作系统或外壳提供“原始”命令行。 Win32通过GetCommandLine(和大概你必须自己解析它......呃!多么原始!)我认为BeginProcess 应该是CreateProcess
      • @JonathanWakely 呵呵,通常我会避免听到更多有关 Windows 的信息,但这次我好像滑倒了 :D 。嗯,在链接之后,听起来他们也提供了解析器功能,并且它是在 Windows XP 中引入的,所以字符串可能已经被清理过了。 M$ 引入 API 似乎主要是为了回应毫无头绪的客户的投诉,以提供简单的答案并降低他们的开发人员支持成本。
      • @JonathanWakely - 如果这是唯一提供的东西,那将是原始的。但是,如果您同时获得(解析的 argv + 原始命令行,那么该原语如何?如果您必须执行 OP 询问的确切事情,这将很有用 - 将参数传递给不同的程序。如果不是这样,您将不得不做原始的事情来连接 argv 以重新创建原始的完整参数。
      • @user93353,但随后其他程序(或更可能是 CreateProcess 函数)必须重新解析字符串。如果你想稍微修改新进程的参数,添加或删除一些东西,你必须关心引用和转义。 execv() 系列函数允许您传递已经拆分为单独单词的参数数组,这更安全(不可能出现引用或转义问题。)早在 Windows 存在之前,它就已解决。
      • @JonathanWakely 哈哈,如果解决方案先于它,是否存在“问题”?很高兴为想要这样的功能存在的人提供一个平台。当然,它降低了 UNIX 世界中的噪音水平。
      【解决方案5】:

      这就是我将命令行转回 shell args 的方式。有时这很好地回显到输出文件中,以将“使用的参数”与输出一起保存。转义是基本的,对于大多数情况来说已经足够了。

      我在命令 (i=0) 处开始输出。如果您只需要参数等,您可以更改为 (i=1)。

      //you have to free() the result!, returns null if no args
      char *arg2cmd(int argc, char** argv) {
          char *buf=NULL;
          int n = 0;
          int k, i;
          for (i=0; i <argc;++i) {
              int k=strlen(argv[i]);
              buf=( char *)realloc(buf,n+k+4);
              char *p=buf+n;
              char endq=0;
              // this is a poor mans quoting, which is good enough for anything that's not rediculous
              if (strchr(argv[i], ' ')) {
                  if (!strchr(argv[i], '\'')) {
                      *p++='\'';
                      endq='\'';
                  } else {
                      *p++='\"';
                      endq='\"';
                  }
              }
              memcpy(p, argv[i], k);
              p+=k;
              if (i < (argc-1)) *p++=' ';
              if (endq) *p++=endq;
              *p='\0';
              n = p-buf;
          }
          return buf;
      }
      

      还有一个简单的 cpp 包装器:

      std::string arg2string(int argc, char **argv) {
          char *tmp=arg2cmd(argc, argv);
          std::string ret=tmp;
          free(tmp);
          return ret;
      }
      

      【讨论】:

        【解决方案6】:

        在 C++/CLI 中是这样的:

        String^ cmdarg = Environment::CommandLine;
        

        【讨论】:

          【解决方案7】:

          如果您使用的是 Windows,我相信正确的解决方案是调用 GetCommandLine() 来获取完整的命令行,然后调用 PathGetArgs(CommandLine) 从开头删除 arg0(您的 exe 路径)。

          【讨论】:

            猜你喜欢
            • 2017-02-24
            • 1970-01-01
            • 2016-02-17
            • 1970-01-01
            • 2021-01-20
            • 2013-08-23
            • 1970-01-01
            • 2014-01-03
            • 2018-06-14
            相关资源
            最近更新 更多