Thread: Pipes and stdout

  1. #1
    Registered User
    Join Date
    Sep 2006
    Posts
    3

    Pipes and stdout

    Hello, all.

    I'm attempting to write a program in which the program executes a shell command. This shell command then outputs some value to stdout, and I would like to be able to read that output from within my program. I am attempting to achieve this via pipes, and have written the following code:

    Code:
    int desc[2];
    char to[100];
    
    pipe(desc);
    dup2(fileno(stdout),desc[1]);
    
    execv("ifconfig en0 | grep ether",NULL);
    read(desc[0],to,sizeof(to));
    
    close(desc[0]);
    close(desc[1]);
    As best as I can figure, this should result in that command line output being stored in "to".

    The code compiles, runs, and I end up with the variable "to" containing a single '\0' character, rather than grep's output to stdout. Pipe's are a new area for me, and so I'm pretty sure the problem lies in my creation of the file descriptors.

    Any help is greatly appreciated.
    Last edited by tricolaire; 12-07-2012 at 01:10 PM.

  2. #2
    Registered User
    Join Date
    Sep 2006
    Posts
    8,868
    It's easy to pipe stdout to a file, and then read the file normally. I've not seen stdout piped to stdin, but that sounds like what you want to do, if possible.

    Interesting problem, I'll have to follow this thread.

  3. #3
    and the hat of int overfl Salem's Avatar
    Join Date
    Aug 2001
    Location
    The edge of the known universe
    Posts
    39,660
    An example
    Code:
    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    
    #define PIPE_RD 0
    #define PIPE_WR 1
    
    int main ( ) {
      int pipe_fd[2];
      pipe(pipe_fd);
      if ( fork() == 0 ) {
        close(pipe_fd[PIPE_RD]);  // close the end we don't need
        dup2(pipe_fd[PIPE_WR],1); // make stdout the end of the pipe
        execl("/bin/ls", "/bin/ls", "-l", (char*)NULL );
      } else {
        close(pipe_fd[PIPE_WR]);  // close the end we don't need
        char buff[100];
        int n;
        while ( (n=read(pipe_fd[PIPE_RD],buff,sizeof(buff))) != 0 ) {
          printf("%.*s",n,buff);
        }
      }
    }
    > execv("ifconfig en0 | grep ether",NULL);
    exec is NOT a shell.
    It does not interpret pipe characters as joining the stdout of one process to the stdin of another process.
    If you want it to search your PATH, then you need to use execlp or execvp
    If you dance barefoot on the broken glass of undefined behaviour, you've got to expect the occasional cut.
    If at first you don't succeed, try writing your phone number on the exam paper.

  4. #4
    Ticked and off
    Join Date
    Oct 2011
    Location
    La-la land
    Posts
    1,728
    Quote Originally Posted by tricolaire View Post
    Hello, all.

    I'm attempting to write a program in which the program executes a shell command. This shell command then outputs some value to stdout, and I would like to be able to read that output from within my program.
    Based on your code, I think you'd get better results with popen():
    Code:
    #define _POSIX_C_SOURCE 200809L
    #include <stdlib.h>
    #include <sys/types.h>
    #include <sys/wait.h>
    #include <string.h>
    #include <stdio.h>
    
    int main(void)
    {
        FILE   *cmd;
        char   *line = NULL;
        size_t  size = 0;
        ssize_t len;
        int     status;
    
        cmd = popen("ifconfig eth0 | grep '[Ee]ther'", "r");
        if (!cmd) {
            fprintf(stderr, "Failed to run command.\n");
            return 1;
        }
    
        while ((len = getline(&line, &size, cmd)) > (ssize_t)0) {
    
            /* Have 'len' characters in 'line', including the newline. */
    
        }
    
        /* Free the input line buffer. */
        free(line);
        line = NULL;
        size = 0;
    
        /* I/O error? */
        if (ferror(cmd) || !feof(cmd)) {
            pclose(cmd);
            fprintf(stderr, "Error reading the command output.\n");
            return 1;
        }
    
        status = pclose(cmd);
        if (WIFEXITED(status)) {
            /* Normal exit. Check exit status code. */
            if (WEXITSTATUS(status) != 0) {
                fprintf(stderr, "Command exited with exit status %d.\n", WEXITSTATUS(status));
                return 1;
            }
        } else {
            /* Abnormal exit. */
            if (WIFSIGNALED(status))
                fprintf(stderr, "Command died from signal %s.\n", strsignal(WTERMSIG(status)));
            else
                fprintf(stderr, "Command died abnormally.\n");
            return 1;
        }
    
        fprintf(stderr, "Success.\n");
    
        return 0;
    }
    Note that your original code is also POSIX, although getline() is defined in POSIX.1-2008.

    Your original code is utterly broken:
    Quote Originally Posted by tricolaire View Post
    Code:
    int desc[2];
    char to[100];
    
    pipe(desc);
    dup2(fileno(stdout),desc[1]);
    Both pipe() and dup2 can and do fail, but you don't bother to check if they succeed or not. Bad form, but not really an error.

    Quote Originally Posted by tricolaire View Post
    Code:
    execv("ifconfig en0 | grep ether",NULL);
    This is just utterly wrong, see man 3 execv(). The first parameter -- the one you put a shell command in -- is the path to the executable. (If you use execvp(), you can use just the name of the executable.) The second parameter is an array of pointers, starting with the command name, followed by each command line parameter, followed by NULL.

    To actually do what you thought that line does, you'd use
    Code:
    execl("/bin/sh",  "sh", "-c", "ifconfig en0 | grep ether", NULL);
    However, the exec() family of functions replace the current process with the one to be executed. It will never return! Any code you might have in your program following that line would only be run if the execl() failed.

    The way to do it is to first create the communications pipe, then fork(), which creates a child process at that point. (The child process is basically a detached duplicate of the parent process. Any changes you do in the child are not visible in the parent, and vice versa, except for certain exceptions that are irrelevant to the task at hand.) Both parent and child processes continue to execute the code, in their separate ways, with initially the only difference being that the child received 0 from fork(), and the parent a nonzero process ID.

    The child process duplicates the write end of the pipe to its standard output, thus redirecting its output to the pipe. (It also closes the read end of the pipe.) Then it executes the desired command. If the execl() fails, the child exit()s, since the parent is already running.

    The parent process closes the write end of the pipe, and starts reading from the read end of the pipe. When you encounter an end of input (read() returns zero), the command has completed, and the parent process can close the read end of the pipe, and reap the child using waitpid().

    popen() does all of the above for you, plus an extra fdopen() so you get a proper FILE stream instead of just a file descriptor.

    Quote Originally Posted by tricolaire View Post
    Code:
    read(desc[0],to,sizeof(to));
    There is absolutely no guarantee, zero, zilch, nada, that the above call does what you think it does. In fact, with pipes, it frequently does not.

    The man 2 read man-page explains that the read() function can return -1 with errno==EINTR in perfectly normal operation (when a signal is delivered, for example). (If the descriptor was set to nonblocking, then you could also see errno==EAGAIN || errno == EWOULDBLOCK.) It can also return any value between 1 and sizeof to, inclusive, without it being an error; this is called a short read.

    When testing, it is quite possible that in your tests it always works. If you change the command, to one that writes e.g. each character separately (with flushes in between) to standard output, you may only get one character per read(). All this is perfectly normal, and if you are going to use the low-level I/O facilities, you'd better get it right or your code will be buggy -- and if you neglect to even check the return values, it will be buggy in weird and mysterious ways. It won't be fun, just annoying and frustrating. (Especially to the end user, if you simply say "it works for me, it must be something at your end", like many programmers who write this kind of code, do.)

    I recommend using popen() if you only need to either supply input to the external program, or read the output from the external command.

    If you want to both supply input and read the output of the external command (similar to coprocesses in Bash shell), then I can show how to do that using the low-level I/O properly. The difficulty with that is that you must be able to produce the input and consume the output asynchronously; you cannot just write one line then read one line, because the external command may need more than one line of input to produce the next line. If you do that, it is likely the two programs get deadlocked, each waiting for the other, neither one advancing. So, the actual approach I'd personally use depends on the exact use case; on the details how the program consumes and produces the data from and to the external program.

  5. #5
    Registered User
    Join Date
    Sep 2006
    Posts
    3
    Color me sufficiently chastised.

    I read the man pages for execv prior to using it and for some reason it did not sink in that this would happen. I replaced the execv with just a call to system() and now the code works. I will, of course, be adding error checking, which was my intent down the road.

    Thank you all for your help.

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. stdout -> ram?
    By Verdagon in forum C++ Programming
    Replies: 10
    Last Post: 04-26-2011, 12:41 AM
  2. redirect STDOUT to pipes
    By Drogin in forum Linux Programming
    Replies: 2
    Last Post: 03-24-2011, 01:56 PM
  3. stdout with JNI
    By GoLuM83 in forum C Programming
    Replies: 4
    Last Post: 12-14-2006, 01:27 PM
  4. using stdout
    By rebok in forum C Programming
    Replies: 4
    Last Post: 11-05-2004, 06:19 PM
  5. stdout
    By Unregistered in forum C Programming
    Replies: 1
    Last Post: 03-25-2002, 05:43 AM