【问题标题】:How do I pass pipe to a C app as a file?如何将管道作为文件传递给 C 应用程序?
【发布时间】:2025-11-23 00:05:01
【问题描述】:

我想做这样的事情:

nc localhost 1234 | myapp

但我希望 myapp 能够看到从命令的第一部分传递的管道作为文件。

例如:

int main() {
    File *fp;
    fp = /* get pointer file which is the pipe passed as "nc localhost 1234" */
}

这可能吗?

谢谢。

【问题讨论】:

  • 刚刚从stdin阅读。最后一个命令的输出通过管道传送到下一个命令的stdin
  • fileno(stdin) 为您返回stdin 的描述符,或者只使用宏STDIN_FILENO
  • shell 拦截管道符号并创建实际的管道,但不会告诉您的程序。如果你想在你的程序中实现某种类似的功能,你将不得不使用另一个字符并自己解析并实现它。
  • @hebbo a | b 的 shell 的标准行为是将 a 的 stdout 连接到 b 的 sstdin。如果您不希望这样,那么您必须使用fork 自己创建流程并自己创建pipes(使用pipe())等等。
  • 您使用的是 C 还是 C++?你有 C++ 标签,所以你应该阅读std::cin

标签: c++ c file pipe file-descriptor


【解决方案1】:

当您的程序作为管道的一部分启动时, 前一个进程通过管道传输到程序的标准输入。所以你可以 做:

int main(void)
{
    char line[1024];
    fgets(line, sizeof line, stdin);
    printf("First line is: %s\n", line);

    return 0;
}

在 POSIX 系统上,您可以使用isatty 来检查文件描述符是否属于 指终端。例如:

#include <stdio.h>
#include <unistd.h>

int main(void)
{
    if(isatty(STDIN_FILENO))
        puts("stdin connected to terminal");
    else
        puts("piped data to stdin");

    return 0;
}

程序的标准行为是从stdin 读取并写入 stdout。通过坚持这一点,您可以使用 shell 链接您的程序 管道|.

例如cat 期望在没有文件传递的情况下从标准输入中读取 作为论据。 cat 的一个简单实现是:

int main(int argc, char **argv)
{
    FILE *in = stdin;

    if(argc == 2)
    {
        in = fopen(argv[1], "r");
        if(in == NULL)
        {
            perror("fopen");
            return 1;
        }
    }

    int c;
    while((c = fgetc(in)) != EOF)
        fputc(c, stdout);

    if(in != stdin)
        fclose(in);

    return 0;
}

作为一名程序员,在阅读 stdin 时,我并不关心 用户在终端中输入输入,或者输入是否通过管道输入。相同 写信给stdout 是真的。作为用户,我知道像cat 这样的程序 当没有文件通过时从stdin 读取并写入stdout。因为 这种行为我能做到的

$ echo "hi" | cat | sort

所以你也应该模仿这种行为。

当然,有时最好知道是否涉及管道。例如 git diff 写信给stdout 有颜色和没有颜色 git diff | more。在这种情况下,可以检查stdout 的连接位置 to,但是写stdout的行为是一样的。

#include <stdio.h>
#include <unistd.h>

void print_colored_message(const char *msg)
{
    printf("\x1b[31m%s\x1b[0m\n", msg);
}

void print_message(const char *msg)
{
    if(isatty(STDOUT_FILENO))
        print_colored_message(msg);
    else
        puts(msg);
}

int main(void)
{
    print_message("Hello");
    print_message("World");

    return 0;
}

复制并粘贴此程序并以./test./test | less 执行一次。 你会看到在第一个版本中你得到一个彩色输出,而在 第二个版本你没有。保持不变的是输出是在 stdout.

如果您不希望您的程序以这种方式被链接,那么您必须告诉 你的用户不应该在这样的链条中使用你的程序。

【讨论】:

  • 标准输出方面呢?
  • 只写到标准输出。标准行为以及大多数程序所期望的是,您从标准输入读取并写入标准输出,故事结束。 shell 知道如何将 stdin 和 stdout 连接到终端或管道,并且 shell 根据您执行程序的方式来执行此操作。如果您执行myprog,那么shell 将创建一个进程并将stdin 附加到终端。如果您执行echo hi | myprog,那么shell 会将标准输入连接到管道。这一切都由外壳维护。除非真的有必要,否则你作为程序员不应该担心这些事情。
【解决方案2】:

您是否考虑过使用命名管道?它们就像管道,一个方向,但它们有名称。如果你想要两个方向,你可以做两个管道。你制作这样的管道:

mkfifo /dir/to_prog
mkfifo /dir/from_prog

然后一个程序的输出可以单独通过管道传输到第二个程序,管道的名称可以像普通文件名一样使用:

nc localhost 1234 > /dir/to_prog
myapp -i /dir/to_prog -o /dir/from_prog
cat < /dir/from_prog

除了这三个命令甚至可以在三个单独的 shell 中发出。 myapp.c 可能如下所示:

#include <stdio.h>

int
main(int argc, char *argv[])
{
    int iFd, oFd, opt;

    while (opt = getopt(argc, argv, "i:o:")) != -1) {

        switch (opt) {

        ...

        case 'i':  iFd = open(optarg, O_RDONLY); break;
        case 'o':  oFd = open(optarg, O_WRONLY); break;

        ...
        }
    }

    ...

    read(iFd, inBuf);

    ...

    write(oFd, outBuf);

    ...
}

当我需要能够处理程序中的数据流时,我已经使用过这种方法几次,并且出于某种原因还需要保留它自己的 STDIN 或 STDOUT。如果您希望 myprog 能够从由两个程序写入的管道中读取数据,那么唯一棘手的部分就是:

cat afile > /dir/to_prog
cat anotherFile > /dir/to_prog

并让 myprog 能够读取这两个文件。问题是当第一只猫退出时,它会关闭 /dir/to_prog,myprog 会将其读取为 EOF。许多程序将 EOF 解释为“没有更多的数据时间退出!”但在这种情况下,一旦第二只猫启动 myprog 将能够从 /dir/to_prog 读取更多数据,它只需要知道在看到 EOF 时不要放弃读取。当然,如果两个程序同时写入(或从中读取)命名管道,它们的数据将被随机交错。

【讨论】:

  • 太棒了!这样做了。
  • 很好的答案,来自我。我没有想到命名管道。
【解决方案3】:

在 BASH 中,您可以使用 temporary fifo 功能:

myapp <( nc localhost 1234 )

在 C 语言中,如果您想将标准输入读取为 FILE 指针,只需使用 stdin

#include <stdio.h>

int main() {
    FILE *fp;
    fp = stdin;
}

如果您想直接在 C 中重定向自定义命令的输出,请使用 popen

【讨论】:

    最近更新 更多