42 Project | System Programming & Process Management
Objective: Create a simplified version of bash, implementing command execution, pipes, redirections, environment variables, and signal handling.
Minishell is one of the most complex 42 projects, requiring deep understanding of system programming, process management, and shell internals. This project implements a functional shell that can execute commands, handle pipes, redirections, environment variables, and built-in commands.
- Process Management:
fork(),execve(),waitpid(), process groups - Inter-Process Communication: Pipes, file descriptor manipulation
- Signal Handling:
SIGINT(Ctrl+C),SIGQUIT(Ctrl+), signal blocking - File I/O: Redirections (
>,>>,<), heredoc (<<), file descriptor management - Parsing: Complex command line parsing, tokenization, syntax validation
- Control Flow: Logical operators (
&&,||), command chaining, subshell execution - Environment Variables: Variable expansion,
$?,$$,$USER, etc. - Built-in Commands:
cd,echo,pwd,export,unset,env,exit - Error Handling: Proper error messages, exit codes, error recovery
- Execute external programs via
execve() - PATH resolution for command lookup
- Proper argument passing
- Exit code handling
- Multiple pipes in sequence (
cmd1 | cmd2 | cmd3) - Proper file descriptor management
- Process synchronization
- Signal handling in pipelines
- Output redirection (
>) - Append redirection (
>>) - Input redirection (
<) - Heredoc (
<<) - Multiple redirections
- Redirection with pipes
- Variable expansion (
$VAR,${VAR}) - Special variables (
$?,$$,$USER) - Variable assignment (
export VAR=value) - Variable unsetting (
unset VAR)
cd- Change directory (with-support)echo- Print with-nflagpwd- Print working directoryexport- Set environment variablesunset- Remove environment variablesenv- Print environmentexit- Exit shell with code
- Logical operators (
&&,||) - Parentheses for command grouping
- Quoting (
',") - Signal handling (Ctrl+C, Ctrl+D, Ctrl+)
- Proper error messages
- Exit code propagation
minishell/
├── src/
│ ├── exec.c # Command execution and process management
│ ├── exec_child.c # Child process setup and execution
│ ├── parser.c # Command parsing and AST building
│ ├── lexer.c # Tokenization
│ ├── expand.c # Environment variable expansion
│ ├── builtins/ # Built-in command implementations
│ └── ... # Additional source files
├── libft/ # Custom library functions
├── dprintf/ # Printf implementation for error output
├── minishell.h # Main header file
├── types.h # Type definitions
└── Makefile # Build configuration
Key Components:
- Parser: Builds abstract syntax tree from command line input
- Executor: Manages process creation, pipes, and redirections
- Built-ins: Implements shell built-in commands
- Expander: Handles environment variable expansion
1. Lexer (lexer.c)
- Tokenizes input string
- Identifies commands, operators, redirections
- Handles quotes and special characters
2. Parser (parser.c)
- Builds abstract syntax tree
- Validates syntax
- Organizes commands and operators
3. Executor (exec.c, exec_child.c)
- Executes command trees
- Manages process creation
- Handles pipes and redirections
- Waits for process completion
4. Built-ins (builtins/)
- Implements shell built-in commands
- Handles environment variable manipulation
- Manages shell state
5. Expander (parser_expand.c)
- Expands environment variables
- Handles special variables
- Processes quotes
6. Signal Handler (signal.c)
- Handles SIGINT (Ctrl+C)
- Handles SIGQUIT (Ctrl+)
- Manages signal blocking during execution
See lexer.c for input tokenization and operator identification.
See exec.c for fork and execute implementation.
See exec_child.c for pipe setup and file descriptor management.
See signal.c for signal handler setup and blocking.
Managing multiple processes in a pipe chain
I tracked pipe head, waited only after all commands in chain are forked, and checked next connector next->con to determine end of pipe chain
Expanding variables within quoted strings correctly
I initially expanded all variables in the parser stage before execution. When I tried to implement logical operators (&& and ||), this caused bugs because expansion needed to happen after command execution to properly handle exit codes and variable values that depend on previous commands. I moved the expansion logic to the execution stage. I tracked the quote state during parsing and only expanded variables when appropriate based on the current quote context
Ctrl+C should interrupt the current command, not exit the shell
I looked for signal-safe options but couldn't find an alternative. I later learned that readline is documented in glibc as signal-safe and was used only to interrupt readline, so I used readline functions to interrupt readline. I blocked signals in the parent process, allowed them in child processes, and restored handlers after execution completes
Signal handling causing zombie processes
I had not added signal handling in command execution (although I did implement it in heredoc), which caused the shell to receive Ctrl+C signals when they were supposed to be received by the child process, like for cat. This caused zombie processes to exist. I used watch -n 0.2 pstree -p <pid> to check this and later resolved it.
Properly closing file descriptors to prevent leaks
I tracked all opened file descriptors and made sure to close them in both parent and child processes appropriately
Proper error messages matching bash behavior
I studied bash error messages and implemented matching error handling to ensure compatibility
# Build minishell
make
# Run minishell
./minishellThe project includes comprehensive testing:
- scan-build - Static analysis tool used during development to catch memory leaks, uninitialized variables, and other potential bugs
- AddressSanitizer (ASan) - Runtime memory error detector used to catch buffer overflows, use-after-free, and other memory safety issues
- minishell_tester - Community tester with optimal test cases
- 42_minishell_tester - Community tester with extensive test cases
- Custom test cases for edge cases and error handling
Test Coverage:
- Command execution
- Pipes and redirections
- Built-in commands
- Environment variables
- Signal handling
- Error handling
- Edge cases
- Robust Parsing: Handles complex command lines with proper syntax validation
- Efficient Execution: Proper process management and resource cleanup
- Signal Safety: Correct signal handling preventing hangs and crashes
- Memory Management: Careful allocation and deallocation, leak-free
- Error Recovery: Graceful error handling with appropriate exit codes
- Bash Compatibility: Matches bash behavior for common operations
This project demonstrates mastery of system programming, process management, and complex software architecture in C.