This project has been created as part of the 42 curriculum by malhassa and yabuawad.
Minishell is a small Unix shell written in C. It is divided into parsing and execution. The parsing part handles user input, and variable expansion. The execution part runs the parsed commands and built-ins.
The goal of the project is to reproduce the core behavior of a basic shell while learning how shells work at a low level.
parsing shell input is handled through three core phases: tokenisation, parsing and expansion.
each phase transforms the input into a more structured and usable form, making it easier to eventually execute the command.
here are some details about each phase:
tokenising is the process of breaking down a stream of text -the command line- into smaller pieces called tokens. i initially stored these tokens in a 2d array as a starting point. by converting raw text into manageable chunks, the later phases become much easier to handle.
minishell$: ls -l | grep Aprtokenised into:
[ ls ]
[ -l ]
[ | ]
[ grep ]
[ Apr ]this phase also includes detecting basic syntax errors.like:
minishell$: ls |
syntax error near unexpected token |this phase is responsible for assigning a type to each token.
the classification depends on the token itself and its surrounding context (what comes before and after it). this allows us to understand the role of each token in the command.
the data structure used here is a linked list with a custom structure (see minishell.h).
this step transforms a simple list of tokens into a structured representation that can be interpreted correctly during execution.
expansion is the phase where tokens are transformed into their final values before execution. this happens after parsing, since we first need to understand the role of each token before modifying its content. the main operations performed during this phase are:
variables like $USER or $PATH are replaced with their corresponding values from the environment(env).
minishell$: echo $USERexpands into:
[ echo ]
[ yara ]quotes determine how the content inside them should be interpreted.
single quotes ' '
everything inside is treated literally — no expansion occurs.
minishell$: echo '$USER'result:
[ echo ]
[ $USER ]double quotes " "
allow expansion while preserving spaces as part of the same token.
minishell$: echo "$USER is here"result:
[ echo ]
[ yara is here ]the special variable $? is replaced with the exit status of the last executed command.
minishell$: echo $?result:
[ echo ]
[ 0 ]this phase ensures that all tokens are fully resolved and ready to be passed to the execution stage.
The execution phase reads through all the commands we parsed and runs them. it checks for heredocs first, then decides how to run each command based on whether it has pipes and redirections.
The basic steps are:
- Check for heredocs: look for '<<' operators and prepare them before running anything
- check for pipes: see if the command has '|' characters
- run the command! : execute based on what we found above
When you use the pipe character ('|'), we run multiple commands at once and connect them together:
- go through each command in the list
- connect them with pipes using the pipe() function
- create a new child process for each command
- each child reads from the previous command and writes to the next one
- wait for all children to finish
Example:
minishell$: cat file.txt | grep mh | sortWhen there are no pipes, commands are executed individually. the execution further branches based on whether redirections are present.
if there are no pipes, we run the command on its own. but we need to check if there are redirections like <, >, <<, or >>.
When a command has redirections (<, >, <<, >>):
-
built-in Commands (like
echo,cd,export):- save the current file descriptors (stdin, stdout)
- change where it reads/writes based on the redirections
- run the command with the new read/write locations
- restore the original file descriptors so the shell continues normally
-
Regular Programs (like
ls,cat, etc.):- fork a child process
- set up the redirections in the child process
- execute the command in the child process
- parent shell waits for completion
Example:
minishell$: echo "mohamed" > output.txt
minishell$: cat < input.txtWhen a command has no pipes and no redirections, we check what kind of command it is:
-
Built-in Commands: Run directly in the shell (no new process needed)
-
Regular Programs: Create a child process and run it there
Example:
minishell$: echo "mohamed"
minishell$: ls -la
minishell$: pwdmakemake clean
make fclean
make re- Main Source : Bash
- Bash manual: https://www.gnu.org/software/bash/manual/
- GNU Readline documentation: https://tiswww.case.edu/php/chet/readline/rltop.html
- Test Cases : https://swift-healer-09e.notion.site/Minishell-Edge-Cases-WIP-19070ced3da3808ca44ec22ec5e41436
- Bash Environment : https://tldp.org/LDP/Bash-Beginners-Guide/html/chap_03.html
AI was used to help explore shell edge cases, and provide support when I was stuck while thinking through a problem. The implementation and final decisions remained under manual review.