运行 'wc' 使用 execvp() 可以识别 /home/usr/foo.txt 但不能识别 ~/foo.txt

Running 'wc' using execvp() recognizes /home/usr/foo.txt but not ~/foo.txt

我有一个程序,我想使用 execvp() 运行 命令 wc

重要的部分在这里:

char *argvNew[5];
argvNew[0] = "wc";
argvNew[1] = "/home/user/foo.txt";
argvNew[2] = NULL;
execvp(argvNew[0], arvgNew);

这工作得很好,终端显示来自 wc 的输出。但是,当我将行更改为:

argvNew[1] = "~/foo.txt"

我在终端中收到此错误:

wc: '~/foo.txt': No such file or directory

运行 wc ~/foo.txt 直接来自终端的行为与 运行ning wc /home/user/foo.txt 完全相同。为什么 execvp~/ 部分有问题?

Tilde (~) 扩展仅通过 shell done/understood - 它不会在 C 程序中扩展。因此 execvp 尝试将 ~ 解释为文件名的一部分(wc 的参数)。

你可以做的是使用 getenv 获取 HOME 目录值并在其前面加上文件名(例如,使用 snprintf)。

当您在命令行键入文件名(例如 ~/foo.txt)时,shell 会在 运行 执行命令之前为您展开它。因此,如果你想让它工作,你必须在你的 C 代码中进行扩展——或者使用 shell 为你进行扩展。 execvp()函数和wc命令都没有问题~;当前目录中根本没有名为 ~ 的目录(因此在不存在的目录中没有名为 foo.txt 的文件)。

编写代码来完成这项工作并不难(尽管根据 POSIX 正确地完成它需要注意细节 — 参见 Tilde expansion)。

然而,也有标准的 POSIX 函数可以帮助完成这项工作,特别是 wordexp(). Although the standard doesn't explicitly mention tilde expansion, it does mention WRDE_NOCMD to suppress command substitution,所以它应该做的很彻底,波浪线扩展应该包含在一个符合标准的文件中实施。

下面是一些简单的测试代码来运行该功能:

#include "stderr.h"
#include <stdio.h>
#include <wordexp.h>

/*
int wordexp(const char *restrict words, wordexp_t *restrict pwordexp,
       int flags);
void wordfree(wordexp_t *pwordexp);
*/

static void do_wordexp(const char *name)
{
    wordexp_t wx = { 0 };
    if (wordexp(name, &wx, WRDE_NOCMD) != 0)
        err_remark("Failed to expand word [%s]\n", name);
    else
    {
        printf("Expansion of [%s]:\n", name);
        for (size_t i = 0; i < wx.we_wordc; i++)
            printf("%zu: [%s]\n", i+1, wx.we_wordv[i]);
        wordfree(&wx);
    }
}

int main(int argc, char **argv)
{
    err_setarg0(argv[0]);

    if (argc <= 1)
        do_wordexp("~/.profile");
    else
    {
        for (int i = 1; i < argc; i++)
            do_wordexp(argv[i]);
    }
    return 0;
}

这里有几个示例 运行(程序 wexp19wexp19.c 构建):

$ wexp19
Expansion of [~/.profile]:
1: [/Users/jonathanleffler/.profile]
$ wexp19 '~informix/bin/oninit' '~/bin/rfmt'  '~/bin/al ~/bin/b?' '~/bin/f?' '$IXD/bin/onstat' ' ~/bin/ow '
Expansion of [~informix/bin/oninit]:
1: [/Users/informix/bin/oninit]
Expansion of [~/bin/rfmt]:
1: [/Users/jonathanleffler/bin/rfmt]
Expansion of [~/bin/al ~/bin/b?]:
1: [/Users/jonathanleffler/bin/al]
2: [/Users/jonathanleffler/bin/bk]
Expansion of [~/bin/f?]:
1: [/Users/jonathanleffler/bin/fd]
2: [/Users/jonathanleffler/bin/fl]
3: [/Users/jonathanleffler/bin/fm]
Expansion of [$IXD/bin/onstat]:
1: [/opt/informix/12.10.FC6/bin/onstat]
Expansion of [ ~/bin/ow ]:
1: [/Users/jonathanleffler/bin/ow]
$

请特别注意扩展引号括起的命令行参数的结果 '~/bin/al ~/bin/b?';它被拆分,单词被单独扩展。倒数第二个参数也从我的环境中扩展了环境变量 $IXD,最后一个示例显示删除了空白。命令行上的单引号对于阻止 shell 做我想演示的 wordexp() 做的事情是必要的。

在 Mac 运行ning macOS 10.13.6 High Sierra 上测试 运行,并使用 GCC 8.2.0。 YMMV!