【问题标题】:Parse command line arguments string into array for posix_spawn/execve将命令行参数字符串解析为 posix_spawn/execve 的数组
【发布时间】:2023-03-21 14:18:01
【问题描述】:

给定表示程序命令行参数的单个字符串cmd,如何获取可以传递给posix_spawnexecve的字符串数组argv

应适当处理各种形式的引用(和转义引号)(产生的调用应与在 POSIX 兼容的 shell 中相同)。对其他转义字符的支持将是可取的。示例:#1#2#3

【问题讨论】:

  • 您使用 [c++] 和 [c] 标记了您的问题。您想要两种语言都合法的解决方案吗?
  • 由于您标记为 C++,您可以使用std::stringstd::string 类有很多有用的解析方法。此外,使用std::string,您不必担心数据的内存管理。
  • 我可以使用可以从 C 代码中使用的库(这包括 C++ 库,因为它可以直接包装它们以供此类使用)。
  • 你要处理多少 shell 语法?变量扩展?命令替换?进程替换? I/O 重定向?在不知道这一点的情况下,很难建议如何进行。

标签: c++ c linux shell posix


【解决方案1】:

正如 Shawn 所评论的,在 Linux 和其他 POSIXy 系统中,您可以使用 wordexp(),它作为此类系统上标准 C 库的一部分提供。比如run.h:

#ifdef __cplusplus
extern "C" {
#endif

/* Execute binary 'bin' with arguments from string 'args';
   'args' must not be NULL or empty.
   Command substitution (`...` or $(...)$) is NOT performed.
   If 'bin' is NULL or empty, the first token in 'args' is used.
   Only returns if fails.  Return value:
     -1: error in execv()/execvp(); see errno.
     -2: out of memory. errno==ENOMEM.
     -3: NULL or empty args.
     -4: args contains a command substitution. errno==EINVAL.
     -5: args has an illegal newline or | & ; < > ( ) { }. errno==EINVAL.
     -6: shell syntax error. errno==EINVAL.
   In all cases, you can use strerror(errno) for a descriptive string.
*/
int run(const char *bin, const char *args);

#ifdef __cplusplus
}
#endif

并将以下 C 源代码编译为链接到 C 或 C++ 程序或库的目标文件:

#define  _XOPEN_SOURCE
#include <stdlib.h>
#include <unistd.h>
#include <wordexp.h>
#include <string.h>
#include <errno.h>

int run(const char *bin, const char *args)
{
    /* Empty or NULL args is an invalid parameter. */
    if (!args || !*args) {
        errno = EINVAL;
        return -3;
    }

    wordexp_t  w;

    switch (wordexp(args, &w, WRDE_NOCMD)) {
    case 0: break;  /* No error */
    case WRDE_NOSPACE: errno = ENOMEM; return -2; 
    case WRDE_CMDSUB:  errno = EINVAL; return -4;
    case WRDE_BADCHAR: errno = EINVAL; return -5;
    default:           errno = EINVAL; return -6;
    }

    if (w.we_wordc < 1) {
        errno = EINVAL;
        return -3;
    }

    if (!bin || !*bin)
        bin = w.we_wordv[0];

    if (!bin || !*bin) {
        errno = ENOENT;
        return -1;
    }

    /* Note: w.ve_wordv[w.we_wordc] == NULL, per POSIX. */

    if (strchr(bin, '/'))
        execv(bin, w.we_wordv);
    else
        execvp(bin, w.we_wordv);

    return -1;
}

例如,run(NULL, "ls -laF $HOME"); 将列出当前用户主目录的内容。环境变量将被扩展。

run("bash", "sh -c 'date &amp;&amp; echo'"); 执行 bashargv[0]=="sh"argv[1]=="-c"argv[2]=="date &amp;&amp; echo"。这让您可以控制要执行的二进制文件。

【讨论】:

  • wordexp() 不仅适用于 Linux,它适用于所有 POSIX 系统 (reference)。
  • @ShaneBishop:非常正确,说得好。改写。 man7.org I linked to (man 3 wordexp) 的手册页在符合部分中确实提到了它,甚至还有一个单独的 POSIX 手册页用于 wordexp (man 3p wordexp)。
  • 当您说没有执行命令替换时,这是不正确的。从您提供到 Linux 手册页的 wordexp 链接中,它说:“完成的扩展包括以下阶段:[...] 命令替换。”
  • @ShaneBishop:不,命令替换是可选的。因为代码使用WRDE_NOCMD 标志,所以没有完成命令替换。请参阅我链接到的手册页(man 3 wordexpman 3p wordexp)。另一个有用的标志是WRDE_UNDEF,如果扩展了未定义的外壳/环境变量,wordexp() 将无法返回WRDE_BADVAL
  • 说了这么多,我自己不使用 wordexp() everything。例如,如果解析具有不适合(或太多)添加到环境中的内部变量的配置文件,我使用状态机来处理引号(单引号和双引号)、反斜杠转义序列(ASCII、十六进制和Unicode)和变量扩展,并根据域用例,算术表达式,使用一个或两个简单的名称-值对哈希表。 (算术函数的第二个。)
猜你喜欢
  • 2019-09-04
  • 2016-07-30
  • 2016-09-25
  • 2016-12-10
  • 2015-09-06
  • 1970-01-01
  • 1970-01-01
  • 2023-04-06
  • 1970-01-01
相关资源
最近更新 更多