c - WEXITSTATUS 在成功案例中返回 popen() 的无效退出状态

标签 c linux bash popen pclose

我正在尝试使用 popen() 获取子进程的存在状态。

案例一:调用函数时shell命令返回错误。这按预期工作。

func("du -sh _invalid_file_");

输出:

du: cannot access '_invalid_file_': No such file or directory
Child exit value: 1

这里的子存在状态与 bash 中运行的 du 的退出值相同。

$ du -sh _invalid_file_
du: cannot access '_invalid_file_': No such file or directory
$ 
$ echo $?
 1
$

案例 2:(错误案例) 使用以下 shell 命令调用函数返回成功。 在我的代码中,WEXITSTATUS() 返回 非零 值,但 bash 中的相同命令返回 0

func("du -sh");

输出:

Child exit value: 141

请建议解决此问题。粘贴下面的代码。

int func(char *cmd)
{
    FILE *pfp = NULL;
    int retval, status;

    pfp = popen(cmd, "r");
    if (pfp == NULL) {
        perror("popen failed");
        exit(1);
    }

    status = pclose(pfp);
    if (status < 0) {
        perror("pclose error");
    } else {
        if (WIFEXITED(status)) {
            retval = WEXITSTATUS(status);
            printf("Child exit value: %d\n", retval);
        }
    }

    return 0;
}

最佳答案

您在实际从中读取任何数据之前关闭了管道。这将导致子进程接收到 SIGPIPE (13) 并被杀死。

popen() 会使用 /bin/sh -c 来运行命令所以它实际上是 sh -c "du ..." 运行。当 duSIGPIPE (13) 杀死时,sh 会将退出状态设置为 128+13=141,因为 du 是 shell session 中的最后一个命令,因此 141 将是 sh -c ... 命令的最终退出状态,您得到 141。

要修复它,您需要在 pclose() 之前从管道中读取数据。

int func(char *cmd)
{
    FILE *pfp = NULL;
    char buf[1024];
    int retval, status;

    pfp = popen(cmd, "r");
    if (pfp == NULL) {
        perror("popen failed");
        exit(1);
    }

    /* read from the pipe or close() may cause the process to receive SIGPIPE */
    while (fgets(buf, sizeof buf, pfp) ) {
        printf("%s", buf);
    }

    status = pclose(pfp);
    if (status < 0) {
        perror("pclose error");
    } else {
        if (WIFEXITED(status)) {
            retval = WEXITSTATUS(status);
            printf("Child exit value: %d\n", retval);
        }
    }

    return 0;
}

以下内容来自man 7 signal:

   Signal     Value     Action   Comment
   ----------------------------------------------------------------------
   ... ...

   SIGPIPE      13       Term    Broken pipe: write to pipe with no
                                 readers; see pipe(7)
   ... ...

这种SIGPIPE 行为每天都会发生,尽管并不明显。你可以这样尝试:

$ od /dev/urandom | head -n1
0000000 067255 052464 166240 113163 122024 054232 015444 110641
$ declare -p PIPESTATUS    # see PIPESTATUS in `man bash'
declare -a PIPESTATUS=([0]="141" [1]="0")
$
$ { od /dev/urandom; echo EXIT=$? >&2; } | head -n1
0000000 020553 063350 025451 116300 006505 151644 122151 175763
EXIT=141

关于 shell 的 128+signal 行为,以下内容来自 man bash (Bash 与 sh 兼容,因此这也适用于 sh。):

When a command terminates on a fatal signal N, bash uses the value of 128+N as the exit status.

关于c - WEXITSTATUS 在成功案例中返回 popen() 的无效退出状态,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/73061212/

相关文章:

linux - 从 Linux 二进制文件返回值

bash - 将变量从一个 shell 脚本导出到 bash 中的另一个脚本

c++ - 混合 C/C++ 源的 Makefile

linux - ll、ls 和 block 大小 M 或 G 之间哪个文件大小最准确?

c - 用 struct 初始化 C 数组

php - 将 Linux Curl 转换为 PHP

linux - 使用 shell 脚本检查文件中给定的字符串

linux - 多字符定界符剪切

c - fork() - 父进程和子进程访问多个文件

c - 在 C 中搜索、排序和打印排序的二维整数数组