下面的答案显示了如何拆分 C 字符串,就像 shell 在将其作为 argc 和 argv 传递给 main 函数之前所做的那样。那就是ls -la会被拆分成:
argv[0] == "ls"
argv[1] == "-la"
但是,由于您想包含多个用管道字符粘在一起的命令行,您首先必须在每个 | 字符处拆分字符串。正如您所提到的,这可以使用strtok 来完成。
str = "ls -la|grep hello";
...
// Do split on |
...
strs[0] == "ls -la";
strs[1] == "grep hello";
然后将这些命令行进一步拆分为它们自己的argv 数组。然后最后按照您的描述将它们组合成一个带有NULL 分隔符的数组。
由于您在开始时不知道最终数组的大小。或者对于有多少 | 符号,您可以首先计算它们并分配一个包含所有 argvs 的数组:
char **strs = NULL;
char **argvs = NULL;
size_t count = 0;
size_t i = 0;
while (*str) if (*str++ == '|') ++count;
strs = calloc(count, sizeof(char *));
...
// strtok on | and save tokens into strs[i]
...
现在拆分命令行并在末尾附加NULL:
// Code for split_commandline below.
for (i = 0; i < count; i++)
{
argvs[i] = split_commandline(strs[i], &argc);
// Make room for NULL at the end of the argv array.
argvs[i] = realloc(argvs[i], (argc + 1) * sizeof(char *));
argvs[i][argc] = NULL;
}
有点做作,当然可以使用存储,但这样做的步骤很明确。
注意:这不会将"ls -la" 拆分为"ls", "-l", "a" 作为原始问题所要求的,而是"ls", "-la"。我不确定为什么需要这样做,但它需要对单个命令进行 hack,因为 "-la" 的含义是特定于 ls 程序的。
分割命令行
Unix/Linux
你可以使用wordexp 来做这件事。但是,这并不是说您应该注意一些安全隐患。也就是说,它会扩展 shell 变量,并且许多(如果不是全部)实现会导致调用 sh。
注意:即使在调用 wordfree 之后,OSX 10.9.5 似乎也会在 wordexp 中泄漏内存。详情请见Is wordexp in libc on OSX 10.9.5 known to leak?。
Windows
我知道这个问题只针对 Linux 进行了标记。但其他人可能对多平台解决方案感兴趣。
在这里您可以使用CommandLineToArgvW。请注意,这是针对 wchar_t * 的,因此下面的示例代码首先从 char * 转换为 wchar_t *,进行拆分,然后再转换回 char *,以便为两个平台获得一致的 API。
写完这个实现后,我还发现__getmainargs 支持char *,但我没有尝试过使用它。
代码示例:
char **split_commandline(const char *cmdline, int *argc)
{
size_t i;
char **argv = NULL;
assert(argc);
if (!cmdline)
{
return NULL;
}
// Posix.
#ifndef _WIN32
{
int ret;
wordexp_t p;
memset(&p, 0, sizeof(p));
// Note! This expands shell variables (might be a security issue).
if ((ret = wordexp(cmdline, &p, 0)))
{
return NULL;
}
*argc = p.we_wordc;
if (!(argv = calloc(*argc, sizeof(char *))))
{
goto fail;
}
for (i = 0; i < p.we_wordc; i++)
{
if (!(argv[i] = strdup(p.we_wordv[i])))
{
goto fail;
}
}
// Note that on some OSX versions this does not free all memory (10.9.5)
wordfree(&p);
return argv;
fail:
p.we_offs = 0;
wordfree(&p);
}
#else // WIN32
{
// TODO: __getmainargs is an alternative... https://msdn.microsoft.com/en-us/library/ff770599.aspx
wchar_t **wargs = NULL;
size_t needed = 0;
wchar_t *cmdlinew = NULL;
size_t len = strlen(cmdline) + 1;
if (!(cmdlinew = calloc(len, sizeof(wchar_t))))
{
goto fail;
}
if (!MultiByteToWideChar(CP_ACP, 0, cmdline, -1, cmdlinew, len))
{
goto fail;
}
if (!(wargs = CommandLineToArgvW(cmdlinew, argc)))
{
goto fail;
}
if (!(argv = calloc(*argc, sizeof(char *))))
{
goto fail;
}
// Convert from wchar_t * to ANSI char *
for (i = 0; i < *argc; i++)
{
// Get the size needed for the target buffer.
// CP_ACP = Ansi Codepage.
needed = WideCharToMultiByte(CP_ACP, 0, wargs[i], -1,
NULL, 0, NULL, NULL);
if (!(argv[i] = malloc(needed)))
{
goto fail;
}
// Do the conversion.
needed = WideCharToMultiByte(CP_ACP, 0, wargs[i], -1,
argv[i], needed, NULL, NULL);
}
if (wargs) LocalFree(wargs);
free(&cmdlinew);
return argv;
fail:
if (wargs) LocalFree(wargs);
free(&cmdlinew);
}
#endif // WIN32
if (argv)
{
for (i = 0; i < *argc; i++)
{
if(argv[i]) free(argv[i]);
argv[i] = NULL;
}
free(argv);
}
return NULL;
}