将命令输出解析为带有文件描述符的变量

Parse command output to a variable with file descriptors

我想让我的程序执行 md5sum 命令来生成给定文件的散列,然后将散列存储到数组 (char *) 变量中。

我读过 popen(),但它涉及 FILE * 个变量,我只想使用文件描述符。

有什么办法吗?

正如我在 , it is perfectly possible to implement the functionality of popen() 中指出的那样,但函数 return 是一个文件描述符,而不是像 popen() 那样的文件流。该任务没有标准库函数。您需要创建管道和叉子。 child 将执行管道,以便命令的标准输出进入管道的写入端(并且读取端关闭),然后执行命令。 parent 将关闭管道的写入端,从管道的读取端读取响应,然后关闭它。这并不是真的那么难——只是有点麻烦,仅此而已。

pclose()对应的代码有点棘手。代码应该等待 child 死亡,还是至少尝试收集僵尸?如果是这样,它如何知道哪个 PID 适合等待?只说“用 returned 文件描述符调用 close()” 很诱人,但这可能会留下僵尸。它应该等待 child 死亡,还是应该在 child 死亡时收集尸体,留给其他代码来处理僵尸?在下面的代码中实现的解决方案:

  • 将文件描述符限制为 128(包括标准 I/O 通道)。
  • 在 fixed-size 数组 pids 中记录与文件描述符关联的 PID。
  • 使用 waitpid() 和保存的与文件描述符关联的 PID,以 0(无条件等待)或 WNOHANG 等待 child。
  • 如果 child 已退出,则报告 child 状态。
  • 否则报告成功 — 0.

改变设计以便动态分配每个文件描述符的 PID 值数组是可行的。您可以控制 dpclose() 函数是等待 child 退出还是在尚未退出时不等待。

该代码不进行信号处理。这是另一层复杂性。

/* SO 6557-1879 */

/* #include "dpopen.h" */
#ifndef DPOPEN_H_INCLUDED
#define DPOPEN_H_INCLUDED

#include <fcntl.h>      /* O_RDONLY or O_WRONLY for mode in dpopen() */
#include <sys/wait.h>   /* WNOHANG for options in dpclose() */

/* dpopen() - similar to popen(), but returning a file descriptor */
/* The value in mode must be O_RDONLY or O_WRONLY */
extern int dpopen(char *cmd, int mode);

/* dpclose() - similar to pclose(), but working with a file descriptor returned by dpopen() */
/* The value in options must be 0 or WNOHANG */
/* The return value is the exit status of the child if available, 0 if not, or -1 if there is a problem */
extern int dpclose(int fd, int options);

#endif /* DPOPEN_H_INCLUDED */

#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>

enum { MAX_PDOPEN_FD = 128 };
static pid_t pids[MAX_PDOPEN_FD];

int dpopen(char *cmd, int mode)
{
    if (cmd == 0 || (mode != O_RDONLY && mode != O_WRONLY))
    {
        errno = EINVAL;
        return -1;
    }

    int fd[2];
    if (pipe(fd) != 0)
        return -1;

    /*
    ** Avoid embarrassment in debug builds if someone closed file
    ** descriptors too enthusiastically, and double check at run-time
    ** for non-debug builds.  In some ways, it isn't very necessary as a
    ** runtime check - the circumstances are implausible.  It is
    ** possible to code around fd[0] == STDIN_FILENO and fd[1] ==
    ** STDERR_FILENO, etc, but it is very messy to do so (having to
    ** avoid closing file descriptors, etc).  It is simpler to close the
    ** two new file descriptors and return -1 with errno set to EINVAL
    ** if they overlap with the standard I/O descriptors.  If this
    ** problem is detected, the program is already screwed up because at
    ** least one of standard input, standard output or standard error
    ** was closed.
    */
    assert(fd[0] > STDERR_FILENO && fd[1] > STDERR_FILENO);
    if (fd[0] <= STDERR_FILENO || fd[1] <= STDERR_FILENO)
    {
        close(fd[0]);
        close(fd[1]);
        errno = EINVAL;
        return -1;
    }
    if (fd[0] >= MAX_PDOPEN_FD || fd[1] >= MAX_PDOPEN_FD)
    {
        close(fd[0]);
        close(fd[1]);
        errno = EMFILE;
        return -1;
    }

    /*
    ** Prepare for forking - minimal step.  See SO 5011-0992
    ** ( and
    **  "Why does forking my
    ** process cause the file to be read infinitely?"
    ** See also SO 0297-9209 ( and
    **  "Using fflush(stdin)",
    ** noting that Standard C and POSIX diverge somewhat; POSIX mandates
    ** behaviour that the C standard does not.  It would be possible to
    ** ensure standard input is 'clean' using code such as:
    **
    **     if (lseek(fileno(stdin), 0L, SEEK_CURR) >= 0)
    **         fflush(stdin);
    **
    ** Standard error is normally not a problem; by default, it is not
    ** fully buffered.
    */
    fflush(stdout);

    pid_t pid = fork();
    if (pid < 0)
    {
        close(fd[0]);
        close(fd[1]);
        return -1;
    }

    if (pid == 0)
    {
        /* Child */
        if (mode == O_RDONLY)
            dup2(fd[1], STDOUT_FILENO);
        else
            dup2(fd[0], STDIN_FILENO);
        close(fd[0]);
        close(fd[1]);
        char *argv[] = { "/bin/sh", "-c", cmd, 0 };
        execv(argv[0], argv);
        exit(EXIT_FAILURE);
    }

    /* Parent */
    if (mode == O_RDONLY)
    {
        close(fd[1]);
        pids[fd[0]] = pid;
        return fd[0];
    }
    else
    {
        close(fd[0]);
        pids[fd[1]] = pid;
        return fd[1];
    }
}

int dpclose(int fd, int options)
{
    if (fd <= STDERR_FILENO || fd >= MAX_PDOPEN_FD || pids[fd] == 0 ||
        (options != 0 && options != WNOHANG))
    {
        errno = EINVAL;
        return -1;
    }
    if (close(fd) != 0)
        return -1;
    pid_t corpse;
    int status;
    pid_t child = pids[fd];
    pids[fd] = 0;
    if ((corpse = waitpid(child, &status, options)) == child)
        return status;
    return 0;
}

int main(void)
{
    int fd1 = dpopen("ls -ltr", O_RDONLY);
    int fd2 = dpopen("cat > ls.out; sleep 10", O_WRONLY);

    if (fd1 < 0 || fd2 < 0)
    {
        fprintf(stderr, "failed to create child processes\n");
        exit(EXIT_FAILURE);
    }

    char buffer[256];
    ssize_t rbytes;
    while ((rbytes = read(fd1, buffer, sizeof(buffer))) > 0)
    {
        ssize_t wbytes = write(fd2, buffer, rbytes);
        if (wbytes != rbytes)
        {
            fprintf(stderr, "Failed to write data\n");
            close(fd1);
            close(fd2);
            exit(EXIT_FAILURE);
        }
    }

    if (dpclose(fd1, WNOHANG) < 0 || dpclose(fd2, 0) < 0)
    {
        fprintf(stderr, "failed to close pipes correctly\n");
        exit(EXIT_FAILURE);
    }

    return 0;
}
  • 另见 和 我的 .
  • 另见 Using fflush(stdin) 和 我的 answer,还注意到 SO 5011-0992 中的信息。