popen()可以像pipe()一样创建双向管道吗?叉()?

发布于 2024-09-26 16:54:49 字数 1235 浏览 7 评论 0原文

我正在用 C++(主要是 C)在模拟文件系统上实现管道。它需要在主机 shell 中运行命令,但在模拟文件系统上执行管道本身。

我可以通过 pipe()fork()system() 系统调用来实现此目的,但我更喜欢使用 < code>popen() (它处理创建管道、分叉进程以及将命令传递给 shell)。这可能是不可能的,因为(我认为)我需要能够从管道的父进程写入,在子进程端读取,从子进程写回输出,最后从父进程读取该输出。我的系统上的 popen() 手册页显示可以使用双向管道,但我的代码需要在旧版本仅支持单向管道的系统上运行。

通过上面的单独调用,我可以打开/关闭管道来实现此目的。可以使用popen()吗?

举个简单的例子,运行ls -l | grep .txt | grep cmds 我需要:

  • 打开管道和进程以在主机上运行 ls -l;读回其输出
  • ls -l 的输出通过管道传输回我的模拟器
  • 打开管道和进程,在主机上的 管道输出上运行 grep .txt ls -l
  • 将其输出通过管道传输回模拟器(卡在此处)
  • 打开管道和进程以在主机上的 grep .txt 管道输出上运行 grep cmds
  • 将其输出传送回模拟器并打印它

ma​​n popen

从 Mac OS X:

popen() 函数“打开”一个 通过创建双向过程 管道、分叉和​​调用 shell。 之前的 popen() 打开的任何流 父进程中的调用已关闭 在新的子进程中。 从历史上看,popen() 已被实现 带单向管;因此, 许多仅 popen() 的实现 允许模式参数指定 阅读或写作,而不是两者兼而有之。因为 popen() 现在使用 双向管道,模式参数 可以请求双向数据流。 mode 参数是一个指向 必须以空字符结尾的字符串 'r' 表示读取, 'w' 表示写入,或者 'r+' 用于读取和写入。

I'm implementing piping on a simulated file system in C++ (with mostly C). It needs to run commands in the host shell but perform the piping itself on the simulated file system.

I could achieve this with the pipe(), fork(), and system() system calls, but I'd prefer to use popen() (which handles creating a pipe, forking a process, and passing a command to the shell). This may not be possible because (I think) I need to be able to write from the parent process of the pipe, read on the child process end, write the output back from the child, and finally read that output from the parent. The man page for popen() on my system says a bidirectional pipe is possible, but my code needs to run on a system with an older version supporting only unidirectional pipes.

With the separate calls above, I can open/close pipes to achieve this. Is that possible with popen()?

For a trivial example, to run ls -l | grep .txt | grep cmds I need to:

  • Open a pipe and process to run ls -l on the host; read its output back
  • Pipe the output of ls -l back to my simulator
  • Open a pipe and process to run grep .txt on the host on the piped output of ls -l
  • Pipe the output of this back to the simulator (stuck here)
  • Open a pipe and process to run grep cmds on the host on the piped output of grep .txt
  • Pipe the output of this back to the simulator and print it

man popen

From Mac OS X:

The popen() function 'opens' a
process by creating a bidirectional
pipe, forking, and invoking the shell.
Any streams opened by previous popen()
calls in the parent process are closed
in the new child process.
Historically, popen() was implemented
with a unidirectional pipe; hence,
many implementations of popen() only
allow the mode argument to specify
reading or writing, not both. Because
popen() is now implemented using a
bidirectional pipe, the mode argument
may request a bidirectional data flow.
The mode argument is a pointer to a
null-terminated string which must be
'r' for reading, 'w' for writing, or
'r+' for reading and writing.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

她如夕阳 2024-10-03 16:54:49

我建议编写您自己的函数来为您执行管道/分叉/系统操作。您可以让该函数生成一个进程并返回读/写文件描述符,如...

typedef void pfunc_t (int rfd, int wfd);

pid_t pcreate(int fds[2], pfunc_t pfunc) {
    /* Spawn a process from pfunc, returning it's pid. The fds array passed will
     * be filled with two descriptors: fds[0] will read from the child process,
     * and fds[1] will write to it.
     * Similarly, the child process will receive a reading/writing fd set (in
     * that same order) as arguments.
    */
    pid_t pid;
    int pipes[4];

    /* Warning: I'm not handling possible errors in pipe/fork */

    pipe(&pipes[0]); /* Parent read/child write pipe */
    pipe(&pipes[2]); /* Child read/parent write pipe */

    if ((pid = fork()) > 0) {
        /* Parent process */
        fds[0] = pipes[0];
        fds[1] = pipes[3];

        close(pipes[1]);
        close(pipes[2]);

        return pid;

    } else {
        close(pipes[0]);
        close(pipes[3]);

        pfunc(pipes[2], pipes[1]);

        exit(0);
    }

    return -1; /* ? */
}

您可以在其中添加所需的任何功能。

I'd suggest writing your own function to do the piping/forking/system-ing for you. You could have the function spawn a process and return read/write file descriptors, as in...

typedef void pfunc_t (int rfd, int wfd);

pid_t pcreate(int fds[2], pfunc_t pfunc) {
    /* Spawn a process from pfunc, returning it's pid. The fds array passed will
     * be filled with two descriptors: fds[0] will read from the child process,
     * and fds[1] will write to it.
     * Similarly, the child process will receive a reading/writing fd set (in
     * that same order) as arguments.
    */
    pid_t pid;
    int pipes[4];

    /* Warning: I'm not handling possible errors in pipe/fork */

    pipe(&pipes[0]); /* Parent read/child write pipe */
    pipe(&pipes[2]); /* Child read/parent write pipe */

    if ((pid = fork()) > 0) {
        /* Parent process */
        fds[0] = pipes[0];
        fds[1] = pipes[3];

        close(pipes[1]);
        close(pipes[2]);

        return pid;

    } else {
        close(pipes[0]);
        close(pipes[3]);

        pfunc(pipes[2], pipes[1]);

        exit(0);
    }

    return -1; /* ? */
}

You can add whatever functionality you need in there.

是伱的 2024-10-03 16:54:49

你似乎已经回答了你自己的问题。如果您的代码需要在不支持 popen 打开双向管道的旧系统上运行,那么您将无法使用 popen (至少不能使用这是提供的)。

真正的问题是有关旧系统的确切功能。特别是,他们的 pipe 支持创建双向管道吗?如果他们有一个可以创建双向管道的 pipe,但 popen 没有,那么我会编写代码的主要流以使用 popen 具有双向管道,并提供 popen 的实现,该实现可以使用双向管道,该双向管道在需要的地方进行编译。

如果您需要支持足够老的系统,pipe 仅支持单向管道,那么您几乎只能使用 pipefork、< code>dup2 等,由您自己决定。我可能仍然会将其包装在一个函数中,该函数几乎就像现代版本的popen一样,但不是返回一个文件句柄,而是用两个文件句柄填充一个小结构文件句柄,一个用于子级的 stdin,另一个用于子级的 stdout

You seem to have answered your own question. If your code needs to work on an older system that doesn't support popen opening bidirectional pipes, then you won't be able to use popen (at least not the one that's supplied).

The real question would be about the exact capabilities of the older systems in question. In particular, does their pipe support creating bidirectional pipes? If they have a pipe that can create a bidirectional pipe, but popen that doesn't, then I'd write the main stream of the code to use popen with a bidirectional pipe, and supply an implementation of popen that can use a bidirectional pipe that gets compiled in an used where needed.

If you need to support systems old enough that pipe only supports unidirectional pipes, then you're pretty much stuck with using pipe, fork, dup2, etc., on your own. I'd probably still wrap this up in a function that works almost like a modern version of popen, but instead of returning one file handle, fills in a small structure with two file handles, one for the child's stdin, the other for the child's stdout.

他是夢罘是命 2024-10-03 16:54:49

POSIX 规定 popen() 调用并非旨在提供双向通信:

popen() 的模式参数是一个指定 I/O 模式的字符串:

  1. 如果mode为r,当子进程启动时,其文件描述符STDOUT_FILENO应为管道的可写端,调用进程中的文件描述符fileno(stream),其中stream为popen返回的流指针(),应为管道的可读端。
  2. 如果mode为w,当子进程启动时,其文件描述符STDIN_FILENO应为管道的可读端,调用进程中的文件描述符fileno(stream),其中stream为popen(返回的流指针) ),应为管道的可写端。
  3. 如果 mode 为任何其他值,则结果未指定。

任何可移植代码都不会做出超出此范围的假设。 BSD popen() 与您的问题描述的类似。

此外,管道与套接字不同,每个管道文件描述符都是单向的。您必须创建两个管道,为每个方向配置一个。

POSIX stipulates that the popen() call is not designed to provide bi-directional communication:

The mode argument to popen() is a string that specifies I/O mode:

  1. If mode is r, when the child process is started, its file descriptor STDOUT_FILENO shall be the writable end of the pipe, and the file descriptor fileno(stream) in the calling process, where stream is the stream pointer returned by popen(), shall be the readable end of the pipe.
  2. If mode is w, when the child process is started its file descriptor STDIN_FILENO shall be the readable end of the pipe, and the file descriptor fileno(stream) in the calling process, where stream is the stream pointer returned by popen(), shall be the writable end of the pipe.
  3. If mode is any other value, the result is unspecified.

Any portable code will make no assumptions beyond that. The BSD popen() is similar to what your question describes.

Additionally, pipes are different from sockets and each pipe file descriptor is uni-directional. You would have to create two pipes, one configured for each direction.

横笛休吹塞上声 2024-10-03 16:54:49

netresolve 后端之一中,我正在与脚本对话,因此我需要写入其 stdin 并从其 stdout 读取。以下函数执行命令,并将 stdin 和 stdout 重定向到管道。您可以使用它并根据您的喜好进行调整。

static bool
start_subprocess(char *const command[], int *pid, int *infd, int *outfd)
{
    int p1[2], p2[2];

    if (!pid || !infd || !outfd)
        return false;

    if (pipe(p1) == -1)
        goto err_pipe1;
    if (pipe(p2) == -1)
        goto err_pipe2;
    if ((*pid = fork()) == -1)
        goto err_fork;

    if (*pid) {
        /* Parent process. */
        *infd = p1[1];
        *outfd = p2[0];
        close(p1[0]);
        close(p2[1]);
        return true;
    } else {
        /* Child process. */
        dup2(p1[0], 0);
        dup2(p2[1], 1);
        close(p1[0]);
        close(p1[1]);
        close(p2[0]);
        close(p2[1]);
        execvp(*command, command);
        /* Error occured. */
        fprintf(stderr, "error running %s: %s", *command, strerror(errno));
        abort();
    }

err_fork:
    close(p2[1]);
    close(p2[0]);
err_pipe2:
    close(p1[1]);
    close(p1[0]);
err_pipe1:
    return false;
}

https://github.com/crossdistro/netresolve/blob/master /backends/exec.c#L46

(我在 popen同时读写)

In one of netresolve backends I'm talking to a script and therefore I need to write to its stdin and read from its stdout. The following function executes a command with stdin and stdout redirected to a pipe. You can use it and adapt it to your liking.

static bool
start_subprocess(char *const command[], int *pid, int *infd, int *outfd)
{
    int p1[2], p2[2];

    if (!pid || !infd || !outfd)
        return false;

    if (pipe(p1) == -1)
        goto err_pipe1;
    if (pipe(p2) == -1)
        goto err_pipe2;
    if ((*pid = fork()) == -1)
        goto err_fork;

    if (*pid) {
        /* Parent process. */
        *infd = p1[1];
        *outfd = p2[0];
        close(p1[0]);
        close(p2[1]);
        return true;
    } else {
        /* Child process. */
        dup2(p1[0], 0);
        dup2(p2[1], 1);
        close(p1[0]);
        close(p1[1]);
        close(p2[0]);
        close(p2[1]);
        execvp(*command, command);
        /* Error occured. */
        fprintf(stderr, "error running %s: %s", *command, strerror(errno));
        abort();
    }

err_fork:
    close(p2[1]);
    close(p2[0]);
err_pipe2:
    close(p1[1]);
    close(p1[0]);
err_pipe1:
    return false;
}

https://github.com/crossdistro/netresolve/blob/master/backends/exec.c#L46

(I used the same code in popen simultaneous read and write)

2024-10-03 16:54:49

下面是代码(C++,但可以轻松转换为 C):

#include <unistd.h>

#include <cstdlib>
#include <cstdio>
#include <cstring>
#include <utility>

// Like popen(), but returns two FILE*: child's stdin and stdout, respectively.
std::pair<FILE *, FILE *> popen2(const char *__command)
{
    // pipes[0]: parent writes, child reads (child's stdin)
    // pipes[1]: child writes, parent reads (child's stdout)
    int pipes[2][2];

    pipe(pipes[0]);
    pipe(pipes[1]);

    if (fork() > 0)
    {
        // parent
        close(pipes[0][0]);
        close(pipes[1][1]);

        return {fdopen(pipes[0][1], "w"), fdopen(pipes[1][0], "r")};
    }
    else
    {
        // child
        close(pipes[0][1]);
        close(pipes[1][0]);

        dup2(pipes[0][0], STDIN_FILENO);
        dup2(pipes[1][1], STDOUT_FILENO);

        execl("/bin/sh", "/bin/sh", "-c", __command, NULL);

        exit(1);
    }
}

用法:

int main()
{
    auto [p_stdin, p_stdout] = popen2("cat -n");

    if (p_stdin == NULL || p_stdout == NULL)
    {
        printf("popen2() failed\n");
        return 1;
    }

    const char msg[] = "Hello there!";
    char buf[32];

    printf("I say \"%s\"\n", msg);

    fwrite(msg, 1, sizeof(msg), p_stdin);
    fclose(p_stdin);

    fread(buf, 1, sizeof(buf), p_stdout);
    fclose(p_stdout);

    printf("child says \"%s\"\n", buf);

    return 0;
}

可能的输出:

I say "Hello there!"
child says "     1      Hello there!"

Here's the code (C++, but can be easily converted to C):

#include <unistd.h>

#include <cstdlib>
#include <cstdio>
#include <cstring>
#include <utility>

// Like popen(), but returns two FILE*: child's stdin and stdout, respectively.
std::pair<FILE *, FILE *> popen2(const char *__command)
{
    // pipes[0]: parent writes, child reads (child's stdin)
    // pipes[1]: child writes, parent reads (child's stdout)
    int pipes[2][2];

    pipe(pipes[0]);
    pipe(pipes[1]);

    if (fork() > 0)
    {
        // parent
        close(pipes[0][0]);
        close(pipes[1][1]);

        return {fdopen(pipes[0][1], "w"), fdopen(pipes[1][0], "r")};
    }
    else
    {
        // child
        close(pipes[0][1]);
        close(pipes[1][0]);

        dup2(pipes[0][0], STDIN_FILENO);
        dup2(pipes[1][1], STDOUT_FILENO);

        execl("/bin/sh", "/bin/sh", "-c", __command, NULL);

        exit(1);
    }
}

Usage:

int main()
{
    auto [p_stdin, p_stdout] = popen2("cat -n");

    if (p_stdin == NULL || p_stdout == NULL)
    {
        printf("popen2() failed\n");
        return 1;
    }

    const char msg[] = "Hello there!";
    char buf[32];

    printf("I say \"%s\"\n", msg);

    fwrite(msg, 1, sizeof(msg), p_stdin);
    fclose(p_stdin);

    fread(buf, 1, sizeof(buf), p_stdout);
    fclose(p_stdout);

    printf("child says \"%s\"\n", buf);

    return 0;
}

Possible Output:

I say "Hello there!"
child says "     1      Hello there!"
維他命╮ 2024-10-03 16:54:49

无需创建两个管道并在每个进程中浪费一个文件描述符。只需使用套接字即可。 https://stackoverflow.com/a/25177958/894520

No need to create two pipes and waste a filedescriptor in each process. Just use a socket instead. https://stackoverflow.com/a/25177958/894520

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文