如何将管道作为文件传递给 C 应用程序?

How do I pass pipe to a C app as a file?

我想做这样的事情:

nc localhost 1234 | myapp

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

例如:

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

这可能吗?

谢谢。

从 BASH 开始,您可以使用 temporary fifo 功能:

myapp <( nc localhost 1234 )

在 C 中,如果你想将标准输入读取为 FILE 指针,那么只需使用 stdin

#include <stdio.h>

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

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

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

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 的连接位置 到,但写入 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.

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

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

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 解释为 "no more data time to quit!" 但在这种情况下,一旦第二只猫启动 myprog 将能够从 /dir/to_prog 读取更多数据,它只需要知道在看到 EOF 时不要放弃读取.当然,如果两个程序同时写入命名管道(或从中读取),它们的数据将随机交错。