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

I would like to do something like this:

nc localhost 1234 | myapp

But I would like myapp to be able to see the pipe passed from the first part of the command as a file.

For example:

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

Is this possible?

Thanks.

3 answers

  • answered 2018-02-13 00:33 dimm

    From BASH you can use the feature temporary fifo:

    myapp <( nc localhost 1234 )
    

    From C, if you want to read standard input as FILE pointer, well just use stdin.

    #include <stdio.h>
    
    int main() {
        FILE *fp;
        fp = stdin;
    }
    

    IF you want to redirect the output from custom command directly in C, use popen.

  • answered 2018-02-13 00:33 Pablo

    When your program is started as part of a pipe, the standard output of the previous process is piped to the standard input of your program. So you just can do:

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

    On a POSIX system, you can use isatty to check if file descriptor belongs refers to a terminal. For example:

    #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;
    }
    

    The standard behaviour of programs is that they read from stdin and write to stdout. By sticking to this, you can chain your programs using the shell's pipe |.

    For example cat expects to read from the standard input if no file is passed as an argument. A simple implementation of cat would be:

    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;
    }
    

    Here as a programmer I don't really care when reading from stdin whether the user types the input in the terminal, or whether the input is piped. The same thing is true of writing to stdout. As a user I know that a program like cat reads from stdin when no file is passed and it writes to stdout. Because of this behaviour I can do

    $ echo "hi" | cat | sort
    

    So you should emulate this behaviour as well.

    Of course, sometimes it is good to know if a pipe is involved. For example git diff writes to stdout with colors and without colors when you do git diff | more. In such a case it's ok to check where stdout is connected to, but the behaviour of writing to stdout is the same.

    #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;
    }
    

    Copy & paste this program and execute it once as ./test and once as ./test | less. You'll see that in the first version you get a colored output whereas in the second version you don't. What remains constant is that the output was done on stdout.

    If you don't want your program to be chained in that way, then you have to tell your users that they shouldn't use your program in such a chain.

  • answered 2018-02-13 00:33 Whilom Chime

    Have you considered using a named pipe? They are like pipes, one directional, but they have names. If you wanted two directions you could just make two pipes. You make the pipes like this:

    mkfifo /dir/to_prog
    mkfifo /dir/from_prog
    

    Then the output of one program can be separately piped to the second and the name of the pipe can be used like a normal filename:

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

    Except that those three commands could even be issued in three separate shells. myprog.c could look like this:

    #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_RDONLY); break;
    
            ...
            }
        }
    
        ...
    
        read(iFd, inBuf);
    
        ...
    
        write(oFd, outBuf);
    
        ...
    }
    

    I've used this method a few times when I've needed to be able to handle a stream of data in a program that also needed to keep it's own STDIN or STDOUT for some reason. The only tricky part comes if you wish myprog to be able to read data from the pipe that was written by two programs, as in:

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

    and have myprog be able to read both files. The issue is that when the first cat exits it closes /dir/to_prog, and myprog will read this as an EOF. A lot of programs interpret EOF as "no more data time to quit!" but in this case once the second cat starts myprog will be able to read more data from /dir/to_prog, it just has to know not to give up reading when it sees EOF. And of course, if two programs write to a named pipe (or read from it) at the same time their data will be randomly interleaved.