- Project Overview
- Global Code Flow
- Code Implementation and Logic
- Common Pitfalls & Debugging Techniques
- Compilation & Execution
- References
Minishell is a Unix shell implementation that mimics Bash behavior. It includes command execution, redirections, pipelines, and signal handling. The goal is to gain a deeper understanding of system programming in C, managing processes, memory, and file descriptors.
- Initialize environment variables by copying them from the system.
- Set up signal handlers to properly manage
Ctrl+C,Ctrl+D, andCtrl+\. - Display the prompt using
readline()and wait for user input.
The core logic of the shell is handled within an infinite loop:
- Read user input using
readline(). - Add command to history with
add_history(). - Parse and tokenize the input into structured commands.
- Expand environment variables (e.g.,
$HOME→/home/user). - Identify and handle built-in commands (e.g.,
cd,echo,exit). - Prepare execution:
- If the command involves pipes (
|), handle it right to left. - If there are redirections (
>,<,>>,<<), adjust file descriptors. - If it’s an external command, fork and execute it using
execve().
- If the command involves pipes (
- Pipes (
|) are processed from right to left, ensuring proper input/output flow. - Redirections (
>,<,>>,<<) are processed before execution, modifying file descriptors withdup2(). - Child processes handle execution while the parent waits and manages input/output.
- Forking: Create a child process to execute external commands.
- Executing Commands: The child process replaces its image with the command using:
execve(const char *pathname, char *const argv[], char *const envp[]);
- Waiting for Child Process: The parent process waits for the child to finish using:
pid_t waitpid(pid_t pid, int *wstatus, int options);
Ctrl+C(SIGINT): Stops the current foreground process but does not exit the shell.Ctrl+D(EOF): Exits the shell if entered on an empty line.Ctrl+\(SIGQUIT): Can be ignored or handled for custom behavior.
- Free allocated memory after every command to prevent leaks.
- Close unused file descriptors to avoid resource exhaustion.
- Properly handle errors to ensure stability.
- The shell must create child processes to execute external commands using
fork():pid_t fork(void);
- The parent process should wait for the child to complete using
waitpid():pid_t waitpid(pid_t pid, int *wstatus, int options);
- If
execve()fails, the child process should terminate gracefully to avoid resource leaks. - To prevent zombie processes, ensure the parent handles terminated child processes correctly.
- Pipes allow one process to send output to another process as input.
- Execution order in pipelines is right to left:
- Example:
cmd1 | cmd2 | cmd3cmd3executes first.cmd2takes input fromcmd3and passes output tocmd1.
- Example:
- A pipe is created using:
int pipe(int pipefd[2]);
- The standard output of the first process is redirected to the pipe:
dup2(pipefd[1], STDOUT_FILENO);
- The second process reads from the pipe via
dup2(pipefd[0], STDIN_FILENO);. - Always close unused pipe ends to avoid resource leaks.
- Exit status management (
Ctrl+C,Ctrl+D): - The shell must properly handle:
SIGINT(Ctrl+C): Interrupts the current process but should not terminate the shell.SIGQUIT(Ctrl+\): Can be ignored or handled differently based on the shell’s design.EOF(Ctrl+D): Should signal an exit if entered on an empty line.
- Signal handlers are set up using:
struct sigaction sa; sa.sa_handler = my_handler; sigaction(SIGINT, &sa, NULL);
- A file descriptor is an integer representing an open file or process I/O stream.
- The shell must correctly handle:
- Opening files with
open():int open(const char *pathname, int flags, mode_t mode);
- Redirecting standard input/output with
dup2():int dup2(int oldfd, int newfd);
- Closing unnecessary file descriptors with
close(fd);.
- Opening files with
errnois a global variable set by system calls when an error occurs.- The shell provides clear error messages using:
which prints the error message corresponding to
void perror(const char *s);
errno. - Alternatively,
strerror()convertserrnointo a readable string:allowing custom error reporting.char *strerror(int errnum);
-
Zombie processes:
- A child process becomes a zombie if the parent does not
waitpid(). - Use
ps aux | grep Zto check for zombies.
- A child process becomes a zombie if the parent does not
-
Pipe mismanagement:
- Ensure
dup2()is called before closing pipe file descriptors. - Always
close(pipefd[0])in the writing process andclose(pipefd[1])in the reading process.
- Ensure
-
Incorrect signal handling:
- Reset signal handlers after execution to avoid interference with future commands.
-
Error handling issues:
- Always check return values of system calls (
open(),dup2(),execve()). - Use
strerror(errno)for custom error messages when logging errors.
- Always check return values of system calls (
make && ./minishellTo clean up compiled files:
make cleanTo remove all binaries and object files:
make fcleanTo recompile from scratch:
make re