[42-Cursus] Minishell is a minimalist shell implementation written in C, designed to mimic basic functionalities of a Unix shell like Bash. This project focuses on understanding processes, file descriptors, and shell operations such as command execution, redirections, pipes, and signal handling.
Keywords
- Shell
- Process Management
- File Descriptors
- Redirections
- Pipes
- Signal Handling
- Built-in Commands
- Environment Variables
- Overview
- Features
- Requirements
- Flowchart
- How to Run
- Parser Example
- Summarized Tests
- Detailed Tests
- What I Learned
- Authors
- Acknowledgments
Minishell is a simplified Unix shell implementation created as part of the 42 Cursus curriculum. The project aims to deepen understanding of core operating system concepts such as process management, file descriptors, and shell operations. Minishell supports basic shell functionalities like command execution, input/output redirection, pipes, and environment variable management. It also handles signals like ctrl-C, ctrl-D, and ctrl-\ similar to Bash.
The project is written in C and adheres to strict coding standards, ensuring no memory leaks and robust error handling. Minishell is a great way to explore the inner workings of a shell and gain hands-on experience with low-level system programming.
- Command Execution: Executes commands based on the
PATHvariable or using relative/absolute paths. - Input/Output Redirection: Supports
<,>,<<, and>>for input/output redirection. - Pipes: Implements pipes (
|) to connect the output of one command to the input of another. - Environment Variables: Expands environment variables (e.g.,
$HOME) and$?for the exit status of the last command. - Signal Handling: Handles
ctrl-C,ctrl-D, andctrl-\as in Bash. - Built-in Commands: Implements built-ins like
echo,cd,pwd,export,unset,env, andexit. - Quoting: Handles single (
') and double (") quotes to prevent interpretation of metacaracters.
- Logical Operators: Supports
&&and||with parentheses for priority. - Wildcards: Implements
*wildcard for the current directory.
- The project must be written in C and follow the 42 Norm.
- No memory leaks are allowed.
- The shell must handle signals correctly and manage file descriptors properly.
- The Makefile must compile the project with
-Wall,-Werror, and-Wextraflags. - The shell must support the mandatory features listed in the subject.
Below is the flowchart representing the architecture and flow of the Minishell project.
Note: This flowchart is an approximation and may not represent the exact implementation details.
graph TB
direction TB
subgraph "User Interface"
Input["Input Handler
(main, input, shell)"]:::black
end
subgraph "Processing Input"
Lexer["Lexer
(lexer)"]:::green
Expansion["Expansion
(expansion_security, expansion_utils, expansion_var)"]:::green
Parser["Parser
(parser_tokenize, parser_utils, token_list, token_utils)"]:::green
end
subgraph "Execution"
subgraph "Redirection Handling"
Redirection["Input & Output
(redirections)"]:::turquoise
Heredocs["Heredocs
(heredoc, heredoc2)"]:::turquoise
end
subgraph "Command Handling"
Builtin["Builtin Commands
(builtin, bi_cd, bi_echo, bi_env, bi_exit, bi_export, bi_pwd, bi_unset)"]:::blue
External["External Commands"]:::blue
end
Command["Command Structure
(cmd, cmd_exit)"]:::dark_blue
Executor["Executor
(execution)"]:::black
end
subgraph "Foundational Utilities"
Utility["Utilities
(utils, utils2, type_check)"]:::orange
Libft["Libft
(ft_*)"]:::orange
Debug["Debug prints
(prints)"]:::orange
end
subgraph "System Interactions"
Env["Environment
(env, env_list, env_var)"]:::red
Signal["Signal Handler
(signal)"]:::red
end
Env --> Input & Expansion & Command & Heredocs
Signal --> Input & Heredocs & Executor
Input -->|"input line"| Lexer
Lexer -->|"validated input"| Expansion
Expansion -->|"expanded vars"| Parser & Heredocs
Parser -->|"tokenized input"| Heredocs & Redirection
Heredocs --> Redirection
Heredocs -->|"prepared heredocs"| Builtin & External & Command
Redirection -->|"prepared redirections"| Builtin & External & Command
Builtin -->|"built-in execution"| Command
External -->|"external execution"| Command
Command -->|"execution context"| Executor
Executor -->|"wait for input after execution"| Input
classDef white fill:#ffffff,stroke:#000000,stroke-width:1px,color:#000000;
classDef black fill:#000000,stroke:#ffffff,stroke-width:1px,color:#ffffff;
classDef green fill:#80ff80,stroke:#008000,stroke-width:1px,color:#004d00;
classDef blue fill:#80bfff,stroke:#004080,stroke-width:1px,color:#00264d;
classDef dark_blue fill:#ffffff,stroke:#004080,stroke-width:1px,color:#00264d;
classDef turquoise fill:#80ffff,stroke:#008080,stroke-width:1px,color:#004d4d;
classDef orange fill:#ffcc80,stroke:#ff8000,stroke-width:1px,color:#663300;
classDef red fill:#ff8080,stroke:#800000,stroke-width:1px,color:#4d0000;
-
Clone this repository:
git clone [repository-url] cd minishell -
Compile the project:
make
-
Run the shell:
./minishell
Input:
cat < in | grep "Hi bye" | grep 'H' > out | cat >> final_out -eTokenizer result and classification:
"cat": command"<": redir_in"in": file_path"|": op_pipe"grep": command"Hi bye": argument"|": op_pipe"grep": command"H": argument">": redir_out"out": file_path"|": op_pipe"cat": command">>": redir_append"final_out": file_path"-e": argument
time: Usetimeto measure how long commands take to execute in Minishell, especially with commands likesleep. This helps confirm correct handling of concurrency and pipes.env: Runenvinside Minishell to verify that environment variables (likePATH) are correctly inherited and available to child processes.env -i: Launch Minishell with an empty environment usingenv -i ./minishellto test its behavior when no environment variables are set.echo $?: After running commands, useecho $?to check if Minishell correctly sets and updates the exit status.which ls: Usewhichwithin Minishell to confirm that the shell can locate executables using the currentPATH.cp /usr/bin/ls .: Copy a binary likelsto the current directory and run it from Minishell to test support for relative and absolute paths.unset PATHorexport PATH="": Remove or clear thePATHvariable in Minishell to ensure it handles missingPATHgracefully (e.g., only absolute/relative paths work).valgrind --trace-children=yes --track-fds=yes: Run Minishell under Valgrind with these flags to check for memory leaks and proper file descriptor management, including child processes.strace: Usestraceto debug Minishell’s system calls and signal handling.strace -f ./minishell: Track all child processes created byfork().strace -e trace=execve ./minishell: Filter and display only command execution (execve) calls.strace -c ./minishell: Get a summary of all system calls and time spent on each.
| Input Command | Description |
|---|---|
""ec''ho"" "Welcome to our 'minishell', I am $USER" |
Prints 'Welcome to our 'minishell', I am <USER>' with mixed quotes. |
export VAR="cat << EOF" VAR1 VAR2= VAR3=""$VAR env | grep VAR export | grep VAR |
Creates new VARS, expands $VAR, and shows the difference between env and export |
mkdir -p t/t/cd t/t/pwdcd -env | grep PWD |
Checks cd behavior in different scenarios. |
echo "exit -1" > our_file./minishell < our_fileecho $? |
Exits a non-interactive Minishell with status 255. |
cat <<lim<<$VAR | tee outWe are finishinglimThanks for watching$VAR |
Uses multiple nested heredocs. Only the last one is saved, but all are processed. |
exit bye |
Exits the shell with an error. |
| Input Command | Description | Expected Output |
|---|---|---|
echo "Hello" |
Prints "Hello" in interactive mode. | Hello |
ls |
Lists files in the current directory. | List of files. |
echo "Hello" | grep "H" | wc -l |
Pipes "Hello" through grep and counts lines. |
1 |
ls -la | grep "txt" | wc -l |
Lists files, filters .txt files, and counts them. |
Number of .txt files. |
cat < input.txt | grep "pattern" | wc -l |
Reads from input.txt, filters lines with "pattern", and counts them. |
Number of matching lines. |
| Input Command | Description | Expected Output |
|---|---|---|
echo "Hello" | ./minishell |
Pipes "Hello" into Minishell in non-interactive mode. | Hello: command not found |
echo ls | ./minishell |
Pipes ls output into Minishell. |
List of files. |
echo "exit 42" | ./minishell |
Exits Minishell with status 42. |
Shell exits with code 42. |
echo "invalid_command" | ./minishell |
Runs an invalid command in non-interactive mode. | Error: command not found, exit code 127. |
echo "echo $SHLVL" | ./minishell |
Prints the shell level in non-interactive mode. | 1 (or incremented value if nested). |
| Input Command | Description | Expected Output |
|---|---|---|
cat < input.txt > output.txt |
Reads from input.txt and writes to output.txt. |
No output, but output.txt contains the content of input.txt. |
echo "Hello" > file.txt | cat < file.txt |
Writes "Hello" to file.txt and reads it back. |
Hello |
echo "Hello" >> file.txt | cat < file.txt |
Appends "Hello" to file.txt and reads it back. |
Hello appended to the existing content. |
grep "pattern" < input.txt > output.txt |
Filters lines with "pattern" and writes to output.txt. |
No output, but output.txt contains matching lines. |
cat << EOF > output.txtHelloEOF |
Writes "Hello" to output.txt using a heredoc. |
No output, but output.txt contains Hello. |
cat << EOF | cat << EOLHelloEOFWorldEOL |
Uses nested heredocs to try write "Hello" and "World". Only the last word is saved. | World, the result is piped, and both heredoc contents are processed. |
cat <<L1 <<L2 <<L3 | tee outFirstL1SecondL2ThirdL3 |
Uses multiple nested heredocs. Only the last one is saved, but all are processed. | Third, out contains Third only. |
| Input Command | Description | Expected Output |
|---|---|---|
export VAR="Hello"echo $VAR |
Sets VAR to "Hello" and prints it. |
Hello |
export VAR="Hello World"echo $VAR |
Sets VAR to "Hello World" and prints it. |
Hello World |
export VAR="Hello"unset VARecho $VAR |
Unsets VAR and attempts to print it. |
No output. |
echo "$PATH" |
Prints the current PATH variable. |
The system's PATH variable. |
export VAR="Hello"echo "$VAR World" |
Expands VAR and appends "World". |
Hello World |
| Input Command | Description | Expected Output |
|---|---|---|
"echo Hello World" |
Fails to print, as bash thinks it is a cmd. | Error: command not found |
echo "Hello ""World" |
Prints 'Hello "World"' with mixed quotes. | Hello World |
""ec''ho"" "Hello World" |
Prints 'Hello "World"' with mixed quotes. | Hello World |
echo "Hello 'World'" |
Prints "Hello 'World'" with mixed quotes. | Hello 'World' |
echo 'Hello "World"' |
Prints 'Hello "World"' with mixed quotes. | Hello "World" |
| Input Command | Description | Expected Output |
|---|---|---|
echo "Hello $USER" |
Expands $USER in double quotes. |
Hello <username> |
echo 'Hello $USER' |
Does not expand $USER in single quotes. |
Hello $USER |
export CMD="echo Hello"$CMD |
Expands $CMD to execute echo Hello. |
Hello |
export CMD="grep pattern"echo "Hello pattern" | $CMD |
Expands $CMD to filter "pattern". |
Hello pattern |
export CMD="cat << EOF"$CMD |
Expands $CMD, but it fails to use a heredoc. |
Error: <</EOF: No such file or directory |
echo "Hello | grep H" |
Treats | as part of the string. |
Hello | grep H |
echo "ls | wc -l" |
Treats ls | wc -l as a literal string. |
ls | wc -l |
export CMD="ls | wc -l"echo "$CMD" |
Prints the value of $CMD without executing it. |
ls | wc -l |
export CMD="ls | wc -l""$CMD" |
Executes ls as cmd, the rest as args (pipes too) |
ls: cannot access '|/wc': No such file or directory |
export CMD="ls | wc -l"$CMD |
Expands $CMD to execute it as a single command |
ls | wc -l: command not found |
| Input Command | Description | Expected Output |
|---|---|---|
cat << EOF$USEREOF |
Uses an unquoted limiter to expand $USER. |
Current username. |
cat << "EOF"Hello $USEREOF |
Uses a quoted limiter to prevent $USER expansion. |
Hello $USER |
cat << 'EOF'Hello $USEREOF |
Uses a single-quoted limiter to treat $USER literally. |
Hello $USER |
| Input Command | Description | Expected Output |
|---|---|---|
export VAR="Hello"unset VARecho $VAR |
Unsets VAR and attempts to print it. |
No output. |
echo -n -nnnnnnn "Hello" |
Prints "Hello" without a newline. | Hello |
env | grep PWD |
Prints environment variables and filters PWD. |
The PWD and OLDPWD variable. |
env |
Displays environment variables that are currently active in the shell. | List environment. Excludes variables declared with export but not assigned a value. |
export |
Displays all exported variables, including those without assigned values. | Includes variables declared with export even without value. |
cd /tmppwd |
Changes to /tmp and prints the current directory. |
/tmp |
cd ..pwd |
Changes to the parent directory and prints it. | Parent directory path. |
cd - |
Switches back to the previous directory. | Path of the previous directory. |
mkdir -p t/t/tcd t/t/trm -rf ../../../tpwdcd ..cd ..cd ..pwd |
Checks cd behaviour in ether | You should be back to the original directory |
| Input Command | Description | Expected Output |
|---|---|---|
ls /nonexistent |
Attempts to list a non-existent directory. | Error: no such file or directory, exit code 1. |
cat /nonexistent/file.txt |
Attempts to read a non-existent file. | Error: no such file or directory, exit code 1. |
echo "Hello" > |
Ends with a redirection without a file. | Error: syntax error near unexpected token \newline', exit code 2`. |
ls | |
Ends with a pipe without a command. | Error: syntax error near unexpected token \newline', exit code 2`. |
chmod 000 non_x_file./non_x_file |
Attempts to execute a file without execute permissions. | Error: permission denied, exit code 126. |
nonexistent_command |
Attempts to run a command that does not exist. | Error: command not found, exit code 127. |
exit 42 |
Exits the shell with status 42. |
Shell exits with code 42. |
exit -1 |
Exits the shell with status 255 (overflow behavior). |
Shell exits with code 255. |
| Input Command | Description | Expected Output |
|---|---|---|
ctrl-C |
Sends SIGINT during an empty prompt. |
Prints a new prompt on a new line. |
ctrl-C during cat |
Sends SIGINT during a blocking command. |
Interrupts cat and prints a new prompt. |
ctrl-\ |
Sends SIGQUIT during an empty prompt. |
Does nothing. |
ctrl-\ during cat |
Sends SIGQUIT during a blocking command. |
Does nothing. |
ctrl-D |
Sends EOF during an empty prompt. |
Exits the shell. |
ctrl-D during cat |
Sends EOF during a blocking command. |
Exits cat and returns to the shell. |
| Input Command | Description | Expected Output |
|---|---|---|
echo $SHLVL |
Prints the current shell level. | 1 (or incremented value if nested). |
./minishell |
Starts a nested Minishell. | $SHLVL is incremented by 1. |
./minishell inside itself |
Starts another nested Minishell. | $SHLVL is incremented by 1. |
env | grep SHLVL |
Prints the SHLVL variable from the environment. |
SHLVL=<value> |
- Process Management: Gained a deeper understanding of how processes are created and managed using
fork,execve, andwaitpid. - File Descriptors: Learned how to manipulate file descriptors for input/output redirection and piping.
- Signal Handling: Implemented signal handling for
ctrl-C,ctrl-D, andctrl-\. - Environment Variables: Managed environment variables and their expansion in commands.
- Error Handling: Developed robust error handling to manage invalid commands, file operations, and memory allocation.
| Name | GitHub Profile | 42 Login |
|---|---|---|
| Oliver King Zamora | OliverKingz | ozamora- |
| Raúl José Pérez Medina | RaulPerezDEV | raperez- |
This project is part of the 42 Cursus, a rigorous programming curriculum that emphasizes hands-on learning and problem-solving. Special thanks to the 42 team for providing this challenging and rewarding project!
Also thanks to peers and mentors for their feedback and support during the development process.
- 42 Community: For the collaborative environment and peer reviews.
- Bash: Used as a reference for shell behavior and functionality.

