Search code examples
cunixpipesystem-callsdup2

Put stdout in a pipe


I want to know the number of time a word is repeat in different file, for that i need to use fork() function and create for each file a child that will find this number and add the different results in the parent.

I successfully find the number occurrences, but can't communicate this information to the parent.

I understand that I need to use pipe and dup2. I used them both previously, but separately, and I couldn't really say that I am comfortable with them.

As you can see with the variable 'temp', the pipe is empty. At first I thought that it was a synchronization problem but it doesn't seem to be the case. It is my understanding that dup2(tube[1],1) put stdout in the pipe, but I am starting to doubt.

What do I miss ?

int main(int argc, char const *argv[])
{
    int tube[2];pipe(tube);int temp;
    int s=0;

    for (int i = 2; i < argc; i++)
    {
        if (fork()==0)
        {
            dup2(tube[1],1);
            close(tube[1]);
            close(tube[0]);
            execlp("grep","grep","-c",argv[1],argv[i],NULL);
        }
    }

    wait(NULL);

    for (int i = 2; i < argc; i++) {
        {
            close(tube[1]);
            close(tube[0]);
            read(tube[0],&temp,sizeof(int));
            printf("temp=%d\n",temp);
            s+=temp;
        }
    }

    printf("s=%d",s);
    return 0;
}

Solution

  • After pipie(), the first file descriptor (FD) returned is the reader side of the pipe, the second FD is the writer side. So, you need to close the first FD in the child path, and the second DF in the parent path of your pogram.

    You must not close the first FD in the parent path, since you want the parent to read what the client(s) wrote. And you must not close the second FD in the child path; how shall it be able to write to the pipe otherwise.

    Remember that stdout is thought to be output for the user, i.e. text. So, your code needs to receive a character string (the count of matching lines), then, test for a valid number, and convert this to an int (long int, long long int), to be able to sum up.

    Also, more than one child process may have written it's result before the parent reads from the pipe, i.e. multiple newline terminated strings may be read in a single read.

    Finally, you need to wait() upon each child process, otherwise they will become zombies.

    The problem made me curious, so I tried to come up with some working code.

    //------------------------------------------------------------------------------
    //
    // The purpose of this program is to find the total number of lines containing
    // the specified grep pattern in all the files specified. 
    //
    // Parameters:
    //  pattern file [ file [ ... ] ]
    //
    //  pattern  ->  search pattern to be passed to grep
    //  file     ->  file(s) to be scanned for "pattern" occurences.
    //
    //------------------------------------------------------------------------------
    
    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    
    int main( int argc, char* argv[] ) {
    
        int pipefd[2];
        int totalCount = 0;
    
        //----------------------------------------
        // Open a pipe to receive the result from
        // running "grep" in the child processes.
        //----------------------------------------
        pipe(pipefd);
    
        //---------------------------------------------------------
        // Start a child process for each file given as parameter. 
        // First file is passed as argument #2.
        //---------------------------------------------------------
        for ( int ii = 2; ii < argc; ii++ ) {
    
            //------------------------
            // This is the child code
            //------------------------
            if ( fork() == 0 ) {
    
                //-----------------------------
                // Redirect stdout to the pipe
                //-----------------------------
                dup2( pipefd[1], 1 ); 
    
                //------------------------------------
                // Close the reader side of the pipe.
                //------------------------------------
                close( pipefd[0] );
    
                execlp( "grep", "grep", "-c", argv[1], argv[ii], NULL ); 
            }
        }
    
        //-----------------------------------------------------------------------
        // This is the parent code. 
        // 
        // There possibly is more than one child process writing to the pipe. 
        // Writes and reads are atomic, however, more than one child may be able
        // to write to the pipe before the parent can read.
        //-----------------------------------------------------------------------
        for ( int ii = 2; ii < argc; ii++ ) {
    
            char result[1024];
            int bytesRead;
            int ss, pp;
    
            //---------------------------
            // Close writer side of pipe
            //---------------------------
            close( pipefd[1] );
    
            //----------------------------------------------------------
            // Read the data that one or more child process has written
            //----------------------------------------------------------
            bytesRead = read( pipefd[0], &result, sizeof( result ) );
            
            if ( bytesRead > 0 ) {
                //---------------------------------------------------------
                // One or more *newline terminated* string has been read
                // from the pipe, representing the result from one or
                // more grep command that has finised.
                //
                // Each newline terminated string is converted to a zero
                // terminated C-sytle string, so that it can be passed to
                // atoi(). 
                //---------------------------------------------------------
                ss = 0;
    
                for ( pp = 0; pp < bytesRead; pp++ ) {
                    if ( result[pp] == '\n' ) {
                        result[pp] = 0x0;
                        totalCount += atoi( &result[ss] );
                        ss = pp + 1;
                    }
                }
            }
    
            wait( NULL );
        }
    
        //-----------------------------------
        // Print the final result and return
        //-----------------------------------
        printf("Total number of matches: %d\n\n", totalCount );
        return 0;
    }
    

    Edit from April 19th: Removed some leftover from intermediate code version. Clarified the conversion from newline terminated to zero terminated string.