Pipex is a project that recreates the shell’s ability to execute commands connected by pipes (|), handling input/output redirection and process management. It demonstrates how Unix-like systems use processes and file descriptors to build command pipelines.
A process is an instance of a running program. Each process has its own memory space and file descriptors. In Unix, processes can create child processes, communicate via pipes, and execute new programs.
fork() creates a new process (child) that is a copy of the calling process (parent). After fork(), both processes continue running independently. The child receives a return value of 0, while the parent receives the child’s PID.
execve() replaces the current process image with a new program. It is used after fork() in the child process to run a new command. If execve() succeeds, the original program code is replaced; if it fails, the child can handle the error.
pipe() creates a unidirectional data channel (pipe) that can be used for inter-process communication. It returns two file descriptors: one for reading and one for writing. Data written to the write end can be read from the read end.
dup2(oldfd, newfd) duplicates a file descriptor, making newfd refer to the same resource as oldfd. This is used to redirect standard input/output, for example, to make a process read from a pipe instead of the keyboard, or write to a file instead of the terminal.
access() checks a file’s accessibility (existence, permissions) before trying to execute or open it. It helps ensure that a command or file can be used as expected.
wait() and waitpid() are used by the parent process to wait for child processes to finish. waitpid() can wait for a specific child, and both return the exit status of the child, allowing the parent to know if the command succeeded.
-
Parsing Arguments:
The program receives command-line arguments specifying input/output files and commands to execute. -
Setting Up Pipes:
For N commands, N-1 pipes are created to connect the output of one command to the input of the next. -
Forking Processes:
For each command, the program forks a child process. Each child sets up its input/output usingdup2()to connect to the appropriate file or pipe. -
Executing Commands:
In each child,execve()is called to run the command. If it fails, an error is reported. -
Closing File Descriptors:
Unused file descriptors are closed in both parent and child processes to prevent leaks and ensure proper pipe behavior. -
Waiting for Children:
The parent process waits for all child processes to finish usingwaitpid(), collects their exit statuses, and cleans up resources.
fork(): To create separate processes for each command in the pipeline.execve(): To run the actual shell commands in the child processes.pipe(): To connect the output of one command to the input of the next.dup2(): To redirect standard input/output to files or pipes as needed.access(): To check if a command exists and is executable before trying to run it.wait()/waitpid(): To synchronize the parent with its children and retrieve their exit statuses.
Here’s a simple visual representation of how the pipeline works with three commands:

