This project has been created as part of the 42 curriculum by ekamar, azorlutu.
- Description
- Features
- Architecture Overview
- Execution Flow
- Instructions
- Built-in Commands
- Signal Handling
- Redirections and Pipes
- Environment Variables
- Quote Handling
- Edge Cases
- Project Structure
- Resources
- AI Usage Disclosure
Minishell is a simplified shell implementation written in C, inspired by bash. This project is part of the 42 school curriculum and aims to provide students with a deep understanding of how Unix shells work internally.
The shell reads commands from the user, parses them, expands environment variables, handles redirections and pipes, and executes the commands. It supports both built-in commands (like cd, echo, export) and external programs found in the system's PATH.
The primary goal of this project is to:
- Understand how a command-line interpreter works
- Learn process creation and management using
fork(),execve(),wait(), andpipe() - Handle signals (
SIGINT,SIGQUIT) properly - Manage file descriptors for redirections
- Implement a lexer/tokenizer and parser for command-line input
| Feature | Description |
|---|---|
| Command History | Remembers previously entered commands using readline library |
| Executable Search | Searches for executables in PATH or uses absolute/relative paths |
| Single Quote Handling | Prevents interpretation of meta-characters inside '...' |
| Double Quote Handling | Prevents interpretation of meta-characters except $ inside "..." |
| Environment Variable Expansion | Expands $VAR and $? (last exit status) |
Input Redirection (<) |
Redirects input from a file |
Output Redirection (>) |
Redirects output to a file (overwrites) |
Append Redirection (>>) |
Redirects output to a file (appends) |
Here Document (<<) |
Reads input until a delimiter is encountered |
Pipes (|) |
Connects stdout of one command to stdin of another |
| Signal Handling | Proper handling of Ctrl+C, Ctrl+D, Ctrl+\ |
| Built-in Commands | echo, cd, pwd, export, unset, env, exit |
The project uses several key data structures defined in includes/minishell.h:
typedef struct s_token
{
t_token_type type; // TOKEN_WORD, TOKEN_PIPE, TOKEN_REDIRECT_*, etc.
t_quote_type quote; // NO_QUOTE, SINGLE_QUOTE, DOUBLE_QUOTE
char *value; // The actual string value
struct s_token *next; // Linked list pointer
} t_token;typedef struct s_command
{
char **args; // Command arguments array
t_redirection *redirects; // Linked list of redirections
struct s_command *next_pipe; // Next command in pipeline
int is_quoted_empty_command; // Handle "" or '' edge case
} t_command;typedef struct s_redirection
{
t_token_type type; // Redirection type
char *filename; // Target filename
t_quote_type redir_quote; // Quote type for heredoc
char *delimiter_raw; // Raw heredoc delimiter
char *delimiter_expanded; // Expanded heredoc delimiter
int fd; // File descriptor
struct s_redirection *next; // Next redirection
} t_redirection;typedef struct s_env
{
char *key; // Variable name
char *value; // Variable value
struct s_env *next; // Linked list pointer
} t_env;typedef struct s_minishell
{
char *input; // Raw input from readline
t_env *env_list; // Environment variables linked list
t_token *tokens; // Tokenized input
t_command *command_list; // Parsed commands
char **env_array; // Env as array for execve
int last_exit_code; // $? value
int has_syntax_error;// Syntax error flag
pid_t *temp_pids; // Child process IDs
} t_minishell;The source code is organized into 8 main modules:
| Module | Files | Purpose |
|---|---|---|
| main | main.c, minishell.c |
Entry point, shell loop, initialization |
| tokenizer | 6 files | Lexical analysis, converts input to tokens |
| parser | 3 files | Syntactic analysis, converts tokens to command structures |
| expander | 3 files | Environment variable expansion ($VAR, $?) |
| executor | 11 files | Command execution, pipes, redirections, heredocs |
| builtins | 11 files | Built-in command implementations |
| env | 5 files | Environment variable management |
| utils | 3 files | Signal handlers, memory cleanup utilities |
The shell follows a Read-Eval-Print Loop (REPL) pattern:
┌─────────────────────────────────────────────────────────────────────────┐
│ SHELL LOOP │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ 1. READLINE ──► Read user input with prompt "minishell> " │
│ │ │
│ ▼ │
│ 2. TOKENIZER ──► Convert input string to token linked list │
│ │ (TOKEN_WORD, TOKEN_PIPE, TOKEN_REDIRECT_*) │
│ ▼ │
│ 3. EXPANDER ──► Expand $VAR and $? in tokens │
│ │ │
│ ▼ │
│ 4. PARSER ──► Convert tokens to command structure │
│ │ (args, redirections, pipes) │
│ ▼ │
│ 5. EXECUTOR ──► Execute commands │
│ │ - Set up pipes between commands │
│ │ - Handle redirections (<, >, >>, <<) │
│ │ - Fork child processes │
│ │ - Run built-ins or external programs │
│ ▼ │
│ 6. CLEANUP ──► Free memory, wait for children │
│ │ │
│ └────────────────► Loop back to step 1 │
│ │
└─────────────────────────────────────────────────────────────────────────┘
- Input Reading:
readline("minishell> ")reads user input with line editing support - History:
add_history()saves command for arrow-key navigation - Tokenization: Input is split into tokens (words, operators, quotes preserved)
- Expansion: Environment variables are replaced with their values
- Parsing: Tokens are organized into command structures with redirections
- Execution: Commands are executed (fork for external, direct for builtins)
- Cleanup: Memory is freed, file descriptors are closed
- Operating System: Unix-like (Linux, macOS)
- Compiler:
cc(clang or gcc) - Libraries:
readlinelibrary (for command line editing)libft(included in project)
Installing readline on Debian/Ubuntu:
sudo apt-get install libreadline-devInstalling readline on macOS:
brew install readlineClone the repository and compile:
git clone https://github.com/ahmettzorlutuna/minishell.git minishell
cd minishell
makeThe make command will:
- Compile the
libftlibrary - Compile all source files with flags
-Wall -Wextra -Werror - Link with the
readlinelibrary - Produce the
minishellexecutable
Run the shell:
./minishellYou will see the prompt:
minishell>
Example session:
minishell> echo "Hello, World!"
Hello, World!
minishell> ls -la | grep minishell
-rwxr-xr-x 1 user staff 123456 Jan 19 12:00 minishell
minishell> export MY_VAR="42 Istanbul"
minishell> echo $MY_VAR
42 Istanbul
minishell> cat << EOF
heredoc> This is a
heredoc> here document
heredoc> EOF
This is a
here document
minishell> exitmake clean # Remove object files
make fclean # Remove object files and executable
make re # Rebuild everythingValgrind check (memory leaks):
make v
# or
valgrind --show-leak-kinds=all --leak-check=full --suppressions=readline.supp ./minishell| Command | Syntax | Description |
|---|---|---|
echo |
echo [-n] [text...] |
Prints text to stdout. -n suppresses trailing newline |
cd |
cd [path] |
Changes current directory. cd - returns to previous directory |
pwd |
pwd |
Prints current working directory |
export |
export [VAR=value] |
Sets environment variable. Without args, lists all exported variables sorted |
unset |
unset VAR |
Removes environment variable |
env |
env |
Lists all environment variables |
exit |
exit [code] |
Exits shell with optional exit code (modulo 256) |
cd: UpdatesPWDandOLDPWDenvironment variablesexport: Validates variable names (must start with letter or_, contain only alphanumerics and_)exit: Handles numeric validation and "too many arguments" error
The shell handles signals differently based on context:
| Signal | Interactive Mode | During Command Execution | Heredoc |
|---|---|---|---|
Ctrl+C (SIGINT) |
New prompt on new line | Terminates current command | Exits heredoc, returns to prompt |
Ctrl+D (EOF) |
Exits shell | Sends EOF to running command | Exits heredoc |
Ctrl+\ (SIGQUIT) |
Ignored | Terminates with core dump | Ignored |
A single global variable g_signal_flag is used to store the received signal number:
extern int g_signal_flag;This complies with the 42 norm that allows only one global variable for signal handling.
- Normal exit: Exit code of last command
Ctrl+C: Exit code 130 (128 + 2)Ctrl+\: Exit code 131 (128 + 3)
| Operator | Name | Description |
|---|---|---|
< |
Input Redirection | Read input from file instead of stdin |
> |
Output Redirection | Write output to file (overwrite) |
>> |
Append Redirection | Write output to file (append) |
<< |
Here Document | Read input until delimiter line |
Examples:
# Input redirection
cat < input.txt
# Output redirection (creates/overwrites file)
echo "Hello" > output.txt
# Append redirection (creates/appends to file)
echo "World" >> output.txt
# Here document
cat << DELIMITER
This text will be
passed to cat
DELIMITERPipes connect the stdout of one command to the stdin of the next:
# Simple pipe
ls -la | grep ".c"
# Multiple pipes
cat file.txt | grep "pattern" | wc -lImplementation:
- Uses
pipe()system call to create pipe file descriptors fork()creates child processes for each commanddup2()redirects stdin/stdout to pipe ends- Parent process waits for all children with
waitpid()
| Syntax | Description |
|---|---|
$VAR |
Expands to value of VAR |
$? |
Expands to exit status of last command |
"$VAR" |
Expands inside double quotes |
'$VAR' |
NOT expanded (literal string) |
Examples:
minishell> export NAME="John"
minishell> echo Hello $NAME
Hello John
minishell> echo 'Hello $NAME'
Hello $NAME
minishell> echo "Hello $NAME"
Hello John
minishell> false
minishell> echo $?
1- Environment is stored as a linked list (
t_env) - Also converted to array format (
char **env_array) forexecve() - Functions:
get_env_value(),set_env_value(),unset_env_value()
- All characters are treated literally
- No variable expansion
- No escape sequences
- Most characters are treated literally
$triggers variable expansion- Metacharacters like
|,<,>are not interpreted
# Mixed quotes
echo "Hello"'World' # Output: HelloWorld
# Empty quotes
echo "" # Output: (empty line)
# Quotes in middle of word
echo hel"lo wor"ld # Output: hello worldThe shell handles many edge cases:
- Unclosed Quotes: Returns syntax error
- Empty Commands: Properly handled with no error
- Quoted Empty Commands:
""or''as first argument shows error - cd with Pipes:
cd | echo hi- cd doesn't work in child process - Variable Names Starting with Number:
export 1VAR=xshows error - Exit with Non-numeric Argument: Shows "numeric argument required"
- Exit Modulo 256:
exit 256exits with code 0 - Multiple Redirections: Last redirection of same type takes effect
- Signals in Heredoc: Ctrl+C properly exits heredoc
minishell/
├── Makefile # Build configuration
├── README.md # This file
├── includes/
│ └── minishell.h # Header file with all declarations
├── libft/ # Custom C library (42 project)
│ └── ...
└── srcs/
├── main/
│ ├── main.c # Entry point, shell loop
│ └── minishell.c # Initialization functions
├── tokenizer/
│ ├── tokenizer.c # Main tokenization logic
│ ├── token_list.c # Token linked list operations
│ ├── token_utils.c # Operator and whitespace handling
│ ├── token_utils2.c # Quote length measurement
│ ├── get_combined_token.c # Combined token extraction
│ └── free_tokenizer.c # Token memory cleanup
├── parser/
│ ├── parser.c # Main parsing logic
│ ├── parser_utils.c # Helper functions
│ └── parser_node_utils.c # Command node operations
├── expander/
│ ├── expander.c # Main expansion logic
│ ├── expander_utils.c # Variable extraction
│ └── expander_utils2.c # Exit status expansion
├── executor/
│ ├── executor.c # Pipeline execution
│ ├── executor_utils.c # Path resolution
│ ├── executor_utils2.c # Process management
│ ├── executor_utils3.c # Builtin execution
│ ├── executor_utils4.c # Pipeline forking
│ ├── executor_utils5.c # Pipe count utilities
│ ├── handle_null_commands.c # Empty command handling
│ ├── heredoc.c # Here document handling
│ ├── heredoc_loop.c # Heredoc read loop
│ ├── heredoc_utils.c # Heredoc helpers
│ └── redirection.c # File redirection
├── builtins/
│ ├── cd/
│ │ ├── builtin_cd.c
│ │ └── builtin_cd_utils.c
│ ├── echo/
│ │ └── builtin_echo.c
│ ├── env/
│ │ └── builtin_env.c
│ ├── exit/
│ │ ├── builtin_exit.c
│ │ └── builtin_exit_utils.c
│ ├── export/
│ │ ├── builtin_export.c
│ │ └── builtin_export_utils.c
│ ├── pwd/
│ │ └── builtin_pwd.c
│ ├── unset/
│ │ └── builtin_unset.c
│ └── utils/
│ └── builtin_utils.c
├── env/
│ ├── env_array.c # env to array conversion
│ ├── env_list.c # Linked list operations
│ ├── env_list_utils.c
│ ├── free_env.c # Memory cleanup
│ └── sort_env_list.c # Sorting for export display
└── utils/
├── handle_signals.c # Signal setup functions
├── signal_handlers.c # Signal handler implementations
└── free_loop.c # Per-iteration cleanup
- GNU Bash Manual - Official bash documentation
- POSIX Shell Command Language - POSIX standard for shells
- GNU Readline Library - Documentation for readline
- Process Control:
fork(2),execve(2),wait(2),waitpid(2),exit(3) - File Descriptors:
open(2),close(2),read(2),write(2),dup(2),dup2(2) - Pipes:
pipe(2) - Signals:
signal(2),sigaction(2),kill(2) - Directory:
getcwd(3),chdir(2),opendir(3),readdir(3),closedir(3) - Environment:
getenv(3)
- Writing a Shell in C - Excellent tutorial by Stephen Brennan
- Linux Shell Scripting Tutorial - Understanding shell behavior
- The TTY Demystified - Understanding terminal I/O
- 42 Minishell Subject - Official project subject
- Harm-Smits 42 Docs - Unofficial 42 documentation
AI tools were used in the following aspects of this project:
- Claude (Anthropic) was used to generate this comprehensive README.md file
- The AI analyzed the project structure, source code, and header files to create accurate documentation
- All technical information was verified against the actual codebase
- The minishell implementation was developed by the student(s) without AI assistance in coding
- AI was not used for writing, debugging, or optimizing the shell source code
According to 42's academic integrity policies, this disclosure is provided to maintain transparency about AI usage in the project documentation process.
- ekamar - 42 Istanbul
- azorlutu - 42 Istanbul
This project is part of the 42 school curriculum. See the LICENSE file for details.