A C program that reproduces the behavior of the shell pipe (`|`). This 42 school project uses `fork()`, `pipe()`, and `dup2()` to manage processes and I/O.
This project is a C program that reproduces the behavior of the shell pipe (|). It's a 42 school assignment designed to provide a deeper understanding of process creation and inter-process communication in Unix-like operating systems using system calls like pipe(), fork(), dup2(), and execve().
- Mandatory Part: Replicates the behavior of
< infile cmd1 | cmd2 > outfile. - Bonus Part:
- Supports multiple pipes:
< infile cmd1 | cmd2 | ... | cmdn > outfile. - Supports
here_docinput:cmd1 << LIMITER | cmd2 >> outfile.
- Supports multiple pipes:
- A C compiler (like
gccorclang) make
-
Clone the repository:
git clone https://github.com/jdecorte-be/pipex.git cd pipex -
The project includes a
libftsubmodule. Initialize and update it:git submodule update --init --recursive
-
Compile the program using the
Makefile:# For the mandatory part make # For the bonus part (multiple pipes and here_doc) make bonus
The program takes four arguments: an input file, two commands, and an output file. It redirects the output of the first command to the input of the second command.
Syntax:
./pipex <infile> <cmd1> <cmd2> <outfile>Example:
This is equivalent to the shell command: < infile grep a1 | wc -l > outfile
./pipex infile "grep a1" "wc -l" outfileThe bonus version can handle an arbitrary number of commands chained together.
Syntax:
./pipex <infile> <cmd1> <cmd2> <cmd3> ... <outfile>Example:
Equivalent to: < infile grep a | cat -e | wc -l > outfile
./pipex infile "grep a" "cat -e" "wc -l" outfileThe bonus version also supports here_doc as an input source.
Syntax:
./pipex here_doc <LIMITER> <cmd1> <cmd2> ... <outfile>Example:
Reads from standard input until EOF is entered, then pipes the result through grep and wc.
./pipex here_doc EOF "grep a" "wc -w" outfileThis project revolves around orchestrating multiple processes and managing their input and output streams. The goal is to create a data flow like this:
infile -> [stdin] cmd1 [stdout] -> [pipe] -> [stdin] cmd2 [stdout] -> outfile
This is achieved using four key system calls.
A pipe is a unidirectional data channel that allows one process to send data to another.
int pipe(int pipefd[2]);pipe()creates a pipe and returns two file descriptors in thepipefdarray:pipefd[0]: The read end of the pipe.pipefd[1]: The write end of the pipe.
- Data written to
pipefd[1]can be read frompipefd[0].
To run two commands simultaneously, we need two separate processes. fork() creates a new process by duplicating the calling process.
pid_t fork(void);- In the parent process,
fork()returns the process ID (PID) of the newly created child process. - In the child process,
fork()returns0. - If an error occurs, it returns
-1.
This allows us to execute different code blocks for the parent and child, enabling them to handle cmd1 and cmd2 respectively.
By default, a process reads from standard input (stdin, fd 0) and writes to standard output (stdout, fd 1). dup2() allows us to redirect these streams.
int dup2(int oldfd, int newfd);dup2() makes newfd a copy of oldfd, closing newfd first if necessary. This is the key to connecting our files and pipes to the commands.
- For the first command (child process):
- Redirect
stdinto read frominfile:dup2(infile_fd, STDIN_FILENO). - Redirect
stdoutto write to the pipe:dup2(pipefd[1], STDOUT_FILENO).
- Redirect
- For the second command (parent process):
- Redirect
stdinto read from the pipe:dup2(pipefd[0], STDIN_FILENO). - Redirect
stdoutto write tooutfile:dup2(outfile_fd, STDOUT_FILENO).
- Redirect
The execve() system call replaces the current process image with a new program. This is how we run the user-specified commands (e.g., ls, grep, wc).
int execve(const char *pathname, char *const argv[], char *const envp[]);pathname: The absolute path to the command's executable (e.g.,/bin/ls).argv[]: An array of arguments for the command, with the first element being the command name itself (e.g.,{"ls", "-l", NULL}).envp[]: An array of environment variables, which is needed to find the commandpathnameif it's not an absolute path.
To find the correct pathname, we parse the PATH variable from envp, split it by :, and test each directory to find the executable.
- Hanging Program: If your program hangs, it's almost always because a pipe's file descriptor was not closed correctly. Every process that has access to the pipe must close both ends when it's done with them. The kernel will only signal "end-of-file" on the read end when all write ends have been closed.
- No Output:
printfwrites tostdout. If you have redirectedstdoutusingdup2, your debug messages will go to a file or a pipe, not the terminal. Useperror()or write directly tostderr(fd 2) for debugging messages that should always appear on the console.// perror prints the error message for the last failed system call perror("Error executing command"); // ft_putstr_fd is a useful function from libft ft_putstr_fd("Debug: In child process\n", STDERR_FILENO);
- Command Not Found: Before calling
execve(), useaccess()to check if the command executable exists and has the correct permissions. This allows you to provide a more informative error message, likebash: cmd: command not found. - File Permissions: Handle errors from
open()gracefully. Your program should mimic the shell's behavior wheninfiledoesn't exist oroutfilecannot be written to.
