Search code examples
cbashcommandpipeps

How to pipe in C


So, I'd like to do the following command line in C:

 ps -eo user,pid,ppid 2> log.txt | grep user 2>>log.txt | sort -nk2 > out.txt

But I'm not sure like, at all how could the code be... I'm confused at how do I have to write the output of a command into a file, the correct and the error output...

Also, I have no idea of how should I struct the piping and what to do when pid == -1 or when pid > 0...

My code below:

int main(){

    int fd0[2], fd1[2], pid0, pid1;

    pipe(fd0);
    pid0 = fork();
    if (pid == 0){
        close(1);
        dup(fd0[0]);
        fd_file= open(“./out.txt”, O_WRONLY | O_CREAT | O_TRUNC, 00600);
        execl("sort","-nk2",">fd_file");
        pipe(fd1);
        pid1 = fork();
        if (pid1 == 0){ 
            close(1);
            dup(fd1[0]);
            ...?
        }

    }
    else if (pid == -1){
        perror("ERROR AT SORT!\n");
        exit(1);
    }

    return 0;
}

Solution

  • While chaining two commands can seem simple, it's a bit more tricky to try to chain more. I give a program that can be easily generalized to chain any number of commands. I prefer to keep first process as a parent of all processes.

    Please read comments for more detailed explanations.

    #include <stdio.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <unistd.h>
    #include <fcntl.h>
    
    
    // Better to use define for constants
    #define SUB_PROCESSES_NUMBER 2
    #define FILE_OUT "out.txt"
    #define FILE_LOGS "log.txt"
    
    char *command0[] = {"ps", "-eo" "user,pid,ppid", NULL};
    char *command1[] = {"grep", "^user", NULL}; // "^user" matches lines starting with "user"
    char *command2[] = {"sort", "-nk2", NULL};
    char **commands[SUB_PROCESSES_NUMBER + 1] = {command0, command1, command2};
    
    int main(){
        pid_t pid[SUB_PROCESSES_NUMBER]; // good practice: fork() result is pid_t, not int
        int fd[SUB_PROCESSES_NUMBER][2];
    
        // I recommend opening files now, so if you can't you won't create unecessary processes
        int fd_file_out = open(FILE_OUT, O_WRONLY | O_CREAT | O_TRUNC, 00600);
        if (fd_file_out < 0)
        {
            perror("open(" FILE_OUT ")");
            return 2;
        }
    
        int fd_file_logs = open(FILE_LOGS, O_WRONLY | O_CREAT | O_TRUNC, 00600);
        if (fd_file_logs < 0)
        {
            perror("open(" FILE_LOGS ")");
            close(fd_file_out); // Not necessary, but I like to do it explicitly
            return 2;
        }
    
        for (int i = 0; i < SUB_PROCESSES_NUMBER; i++) // If you decide to add more steps, this loop will be handy
        {
            if (pipe(fd[i]) < 0)
            {
                perror("pipe");
                close(fd_file_out);
                close(fd_file_logs);
                if (i > 0)
                {
                    close(fd[i - 1][0]);
                }
                return 2;
            }
    
            pid[i] = fork();
            if (pid[i] < 0)
            {
                perror("fork()");
                close(fd_file_out);
                close(fd_file_logs);
                if (i > 0)
                {
                    close(fd[i - 1][0]);
                }
                close(fd[i][0]);
                close(fd[i][1]);
                return 2;
            }
    
            if (pid[i] == 0)
            {
                close(fd[i][0]); // First thing to do: close pipes and files you don't need any more
                close(fd_file_out);
    
                close(1);
                dup(fd[i][1]);
                close(fd[i][1]); // duplicated pipes are not useful any more
    
                close(2); // Also need to redirect stderr
                dup(fd_file_logs);
                close(fd_file_logs);
    
                if (i > 0)
                {
                    close(0); // Also need to redirect stdin if this is not first process
                    dup(fd[i - 1][0]);
                    close(fd[i - 1][0]);
                }
    
                execvp(commands[i][0], commands[i]); // In a loop, we need a execv()/execvp()/execvpe() call
                return 2; // Should not be reached;
            }
    
            // sub process either did execvp() or return, he won't reach this point
            close(fd[i][1]);
            if (i > 0)
            {
                close(fd[i - 1][0]);
            }
        }
    
        close(fd_file_logs);
    
        close(0);
        dup(fd[SUB_PROCESSES_NUMBER - 1][0]);
        close(fd[SUB_PROCESSES_NUMBER - 1][0]);
    
        close(1);
        dup(fd_file_out);
        close(fd_file_out);
    
        execvp(commands[SUB_PROCESSES_NUMBER][0], commands[SUB_PROCESSES_NUMBER]);
        perror("execvp");
        return 2;
    }