Skip to content

🐚 42 Minishell β€” evaluated & passed. Norm-compliant shell (mandatory scope + minimal wildcard bonus). Educational reference; not a full Bash.

Notifications You must be signed in to change notification settings

kitearuba/minishell

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

60 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

🐚 Minishell β€” Tiny Bash (42 Project)

Minishell β€’ C β€’ Unix β€’ Signals β€’ Readline

A compact, Bash-like shell in C for the 42 core curriculum. It supports builtins, pipelines, redirections (incl. heredoc), quote-aware expansion, and correct signal/exit-status semantics β€” implemented strictly within the subject (not a full Bash).

  • βœ… Mandatory: complete, defense-ready
  • ✨ Bonus (started): minimal * wildcard expansion (ungraded in our defense)
  • 🚫 Not implemented by design: &&, ||, subshells ( … ), command substitution, assignment words like X=1 cmd

This repository contains the final, evaluated version. The code follows 42 Norm (≀ 25 lines per function, ≀ 5 functions per file, ≀ 4 params per function, formatting rules).


πŸ“‘ Table of Contents

  1. Introduction
  2. Features & Scope
  3. Build & Dependencies
  4. Quick Start
  5. Architecture Overview
  6. Lexing, Parsing & Expansion
  7. Redirections & Heredoc
  8. Signals & Exit Status
  9. Environment Management
  10. Wildcards (Bonus β€” started)
  11. Testing & Example Sessions
  12. Defense Notes
  13. Known Limitations
  14. Changelog (Recent Fixes)
  15. Project Structure
  16. Build Options & Make Targets
  17. Evaluator Quick Checklist
  18. Authors

🧠 Introduction

Minishell re-implements a constrained, Bash-compatible subset focusing on:

  • Correct builtins in the parent process (when not piped)
  • Robust redirections (<, >, >>, <<) with right-most precedence
  • Pipelines |
  • Expansion of $VAR and $? honoring quote rules & token adjacency
  • Signals for prompt / children / heredoc
  • Accurate exit statuses (126, 127, 130, 131)

Design goal: clean, Norm-compliant, defense-ready code that behaves like Bash in the parts covered by the subject.


✨ Features & Scope

  • Builtins (run in parent if not piped):
    • echo (with -n)
    • cd
    • pwd
    • export
    • unset
    • env
    • exit
  • External commands via fork + execve
  • Pipelines: arbitrary length (cmd1 | cmd2 | ...)
  • Redirections:
    • Input <
    • Output >
    • Append >>
    • Heredoc << (quoted/unquoted delimiter semantics)
  • Expansion:
    • $VAR, $?
    • Single quotes block expansion
    • Double quotes allow expansion
    • Adjacent tokens preserved (e.g., "a"'b'c β†’ abc)
  • Signals
    • Prompt: Ctrl-C clears line + sets $?=130; Ctrl-\ ignored
    • Children: Ctrl-C β†’ 130, Ctrl-\ β†’ 131
    • Heredoc: Ctrl-C aborts immediately (status 130), skips command
  • Environment management
    • Deep-copied char **env
    • export sorted printing, strict identifier validation
    • unset safe removal
  • Bonus (started):
    • Minimal * wildcard expansion (no hidden files unless pattern starts with .)

Out of scope per subject:

  • Logical operators &&, ||
  • Subshells ( … )
  • Command substitution `cmd` / $(cmd)
  • Assignment words (X=1 cmd)

πŸ”§ Build & Dependencies

Dependencies

  • readline library
  • 42 libft (included as submodule or folder)

On Linux (Debian/Ubuntu):

sudo apt-get update
sudo apt-get install -y libreadline-dev
make

On macOS (Homebrew):

brew install readline
# The Makefile already links with -lreadline and includes -I as needed
make

Build

make         # build minishell
make clean   # remove object files
make fclean  # remove objects + binary
make re      # full rebuild

πŸš€ Quick Start

./minishell

minishell $> echo hello | grep h
minishell $> export A=42; echo "$A"
minishell $> cat << EOF | grep hi
> hi
> EOF
minishell $> cd -

πŸ— Architecture Overview

Data Structures

typedef struct s_redirection {
    int                  type;       // enum: <, >, >>, << 
    char                *filename;   // for << store delimiter
    struct s_redirection *next;
} t_redirection;

typedef struct s_command {
    char             **argv;         // null-terminated
    t_redirection    *redirection;   // linked list
    struct s_command *next;          // pipeline chain
} t_command;

typedef struct s_bash {
    char      **env;           // deep-copied environment
    int        exit_status;
    t_token   *tokens;         // last tokens (for cleanup)
    t_command *commands;       // last parsed pipeline (for cleanup)
    char     **export_noval;   // names declared via 'export NAME' (no value)
} t_bash;

Control Flow

readline β†’ tokenize β†’ expand β†’ parse β†’ execute
  • Tokenizer: words, quotes, $, redirects, pipe

  • Expander: $VAR, $? under quote rules; concatenates adjacent pieces

  • Parser: builds t_command linked list; attaches redirections; permits redirections at command start (e.g., > out cmd)

  • Executor:

    • No pipe: builtin runs in parent, else fork + execve
    • Pipeline: each stage forks; redirs apply in child; parent reaps and sets status from rightmost child

Execution Rules

  • Right-most redirection wins (echo hi > a > b writes to b)

  • Builtins in parent (when not piped) so that PWD, env, $? update in-shell

  • In pipelines, builtins run in the child of their stage (Bash semantics)

  • cd -:

    • uses $OLDPWD and prints the new cwd
    • updates PWD/OLDPWD

🧩 Lexing, Parsing & Expansion

  • Single quotes '...': literal text, no expansion

  • Double quotes "...": expand $VAR, $?

  • Bare $ inside double quotes remains $ if no valid name follows

  • Adjacency preserved:

    • "a"'b'c β†’ abc
    • filenames for redirections can be built from multiple adjacent pieces without spaces; we concatenate during parse
  • $? expands to last exit status


πŸ“₯ Redirections & Heredoc

  • < β€” open read-only, dup to stdin

  • > β€” create/truncate, dup to stdout

  • >> β€” create/append, dup to stdout

  • << (heredoc)

    • Quoted delimiter β†’ no expansion in heredoc body
    • Unquoted delimiter β†’ expand $VAR and $?
    • Ctrl-C during heredoc cancels immediately (status 130), command is skipped
  • Redirections are applied inside children (including pipeline stages), so each stage has its own view

  • For a chain of >/>>, the last one takes effect (right-most precedence)


πŸͺ“ Signals & Exit Status

  • Prompt

    • SIGINT (Ctrl-C): clear line + newline, set $?=130
    • SIGQUIT (Ctrl-\): ignored
  • Children

    • SIGINT β†’ exit code 130
    • SIGQUIT β†’ exit code 131
  • Exit codes of interest

    • 127 β€” command not found / PATH unset
    • 126 β€” found but not executable or is a directory
    • 130 β€” terminated by SIGINT
    • 131 β€” terminated by SIGQUIT
  • For pipelines, the shell’s $? is the rightmost stage’s status (with the signal mapping above)


🌱 Environment Management

  • We deep-copy the host environment (char **env) at startup

  • export rules (Bash-compatible within scope)

    • export (no args): print a sorted merged view of actual env and names stored in export_noval

      • Format: declare -x KEY="VAL" (if value) or declare -x KEY (if no value)
    • export NAME (no =):

      • Validate identifier (see below)
      • If NAME is not already in env, store it in export_noval (name only)
    • export NAME=VAL:

      • Add/replace in env
      • Remove NAME from export_noval if present
    • Invalid identifiers print to stderr and set status 1

  • Identifier validation:

    • [A-Za-z_][A-Za-z0-9_]*
    • Rejects: A-, HELLO-=123, =, 123, etc.
  • unset NAME removes from env and also from export_noval if present


πŸ”Ή Wildcards (Bonus β€” started)

  • Minimal * expansion implemented (not graded in defense)
  • Uses opendir/readdir
  • Expands only unquoted tokens containing *
  • Hidden files are ignored unless the pattern starts with .

πŸ§ͺ Testing & Example Sessions

We validated behavior with the public LucasKuhn/minishell_tester plus extra manual tests.

Handy Manual Checks

# cd -
pwd; cd ..; cd -; echo "$?"             # prints new cwd; exit 0

# export (name only vs key=val)
unset A; export A; export | grep '^declare -x A$'
export A=1; export | grep '^declare -x A="1"$'
unset A

# invalid export (should set status 1 and print error)
export A-; echo $?
export HELLO-=123; echo $?
export =; echo $?
export 123; echo $?

# pipe + env mutation (env change must not leak back to parent through a pipe)
export GHOST=123 | env | grep GHOST     # parent remains unchanged

# mixed quoting / adjacency
echo "a"'b'c                            # -> abc
echo "$"                                # -> $

# heredoc cancel (Ctrl-C)
cat << EOF
^C                                      # heredoc canceled, $?=130

# signal exit statuses from children
sleep 5 & kill -QUIT %1                 # observe status 131

Pipelines

echo a | cat -e
cat << EOF | grep hi | wc -l
hi
EOF

Redirections

rm -f a b
echo hi > a > b; wc -c a b
# 0 a
# 3 b
# 3 total

grep hi < ./test_files/infile
echo hi > out.txt
echo again >> out.txt

πŸ›‘ Defense Notes

  • We implemented only what the subject requires, not full Bash.

  • Builtins in parent (when not piped) so PWD, env, $? mutate the shell state.

  • Pipelines spawn children per stage; redirs are applied in children; parent reaps and sets $? from the rightmost stage.

  • cd - uses $OLDPWD, prints the new cwd, and updates PWD/OLDPWD.

  • export:

    • Print format: declare -x KEY="VAL" or declare -x KEY (no quotes if no value)
    • Accept export NAME as a declaration (recorded in export_noval unless already in env)
    • Invalid identifiers β†’ stderr, exit 1
  • 126 vs 127: distinguished correctly (not executable vs not found).

  • Heredoc:

    • Quoted delimiter β†’ no expansion
    • Unquoted β†’ expands $VAR/$?
    • Ctrl-C cancels heredoc and sets $?=130 (command skipped)

⚠️ Known Limitations

  • No logical operators (&&, ||), no subshells ( … )
  • No command substitution or assignment words
  • Minimal wildcard only (bonus, not graded)
  • Message strings like β€œBroken pipe” may differ from Bash; not required by subject. Exit codes match.

πŸ“ Changelog (Recent Fixes)

  • cd - parity with Bash

    • Fetch $OLDPWD, change directory, print new cwd
    • Update PWD/OLDPWD
    • If OLDPWD missing β†’ minishell: cd: OLDPWD not set (exit 1)
  • export compliance

    • Added name-only declarations with bash->export_noval
    • export (no args) prints sorted merged view of env + noval
    • Correct quoting: declare -x KEY="VAL" or declare -x KEY
    • Strict identifier validation (reject A-, HELLO-=123, =, 123, …)
    • On NAME=VAL, update env and remove from noval
    • On unset NAME, also remove from noval
  • Pipelines

    • Builtins inside pipeline stages run in the child of that stage
    • Redirections applied per child
    • Parent computes final $? from the rightmost stage
  • Norm refactors

    • Split long functions (e.g., pipeline execution into child_process, pipe_child, wait_for_pipeline)
    • Respected ≀ 25 lines/function, ≀ 5 functions/file, ≀ 4 params/function, line widths, spacing, includes
  • Printing & errors

    • Bash-like formatting for export lines
    • Consistent error messages for cd, invalid identifiers, and heredoc cancel

πŸ“‚ Project Structure

.
β”œβ”€β”€ include/
β”‚   β”œβ”€β”€ minishell.h
β”‚   β”œβ”€β”€ builtin.h
β”‚   └── struct.h
β”œβ”€β”€ libft/                      # 42 libft
β”œβ”€β”€ minishell.c                 # entry (delegates to core)
β”œβ”€β”€ src/
β”‚   β”œβ”€β”€ core/
β”‚   β”‚   β”œβ”€β”€ init_minishell.c
β”‚   β”‚   └── execute_minishell.c
β”‚   β”œβ”€β”€ lexer/
β”‚   β”‚   β”œβ”€β”€ tokenize.c
β”‚   β”‚   β”œβ”€β”€ tokenizer_utils.c
β”‚   β”‚   └── tokenizer_redirects.c
β”‚   β”œβ”€β”€ parser/
β”‚   β”‚   β”œβ”€β”€ parse_tokens.c
β”‚   β”‚   β”œβ”€β”€ parse_utils.c
β”‚   β”‚   β”œβ”€β”€ parser_cmd.c
β”‚   β”‚   β”œβ”€β”€ parser_cmd_utils.c
β”‚   β”‚   β”œβ”€β”€ parser_checks.c
β”‚   β”‚   β”œβ”€β”€ handle_parse_redirection.c
β”‚   β”‚   └── last_token_is_pipe.c
β”‚   β”œβ”€β”€ expand/
β”‚   β”‚   β”œβ”€β”€ expand.c
β”‚   β”‚   β”œβ”€β”€ expand_utils.c
β”‚   β”‚   β”œβ”€β”€ expand_wildcard.c
β”‚   β”‚   β”œβ”€β”€ wildcard_utils.c
β”‚   β”‚   └── wildcard_utils_2.c
β”‚   β”œβ”€β”€ execution/
β”‚   β”‚   β”œβ”€β”€ execve.c
β”‚   β”‚   β”œβ”€β”€ get_cmd_path.c
β”‚   β”‚   β”œβ”€β”€ redirection.c
β”‚   β”‚   β”œβ”€β”€ heredoc.c
β”‚   β”‚   β”œβ”€β”€ heredoc_utils.c
β”‚   β”‚   β”œβ”€β”€ run_external_cmd.c
β”‚   β”‚   β”œβ”€β”€ run_external_cmd_utils.c
β”‚   β”‚   └── pipes/
β”‚   β”‚       β”œβ”€β”€ create_pipe.c
β”‚   β”‚       └── execute_pipeline.c
β”‚   β”œβ”€β”€ builtin/
β”‚   β”‚   β”œβ”€β”€ ft_echo.c
β”‚   β”‚   β”œβ”€β”€ ft_cd.c
β”‚   β”‚   β”œβ”€β”€ ft_pwd.c
β”‚   β”‚   β”œβ”€β”€ ft_env.c
β”‚   β”‚   β”œβ”€β”€ ft_export.c
β”‚   β”‚   β”œβ”€β”€ ft_export_utils.c
β”‚   β”‚   β”œβ”€β”€ ft_unset.c
β”‚   β”‚   └── ft_exit.c
β”‚   β”œβ”€β”€ env/                    # ft_getenv, env_set, etc.
β”‚   β”œβ”€β”€ signal/                 # prompt + child signal handling
β”‚   └── utils/                  # frees, strings, small helpers
β”œβ”€β”€ Makefile
└── README.md

Headers use #include "minishell.h" with -Iinclude in the Makefile.


πŸ›  Build Options & Make Targets

  • make β€” build minishell
  • make clean β€” remove objects
  • make fclean β€” remove objects and binary
  • make re β€” full rebuild

Compile flags (typical 42):

-Wall -Wextra -Werror

The Makefile links -lreadline and includes the correct headers (adjust include paths for your platform if needed).


βœ… Evaluator Quick Checklist

  • Prompt shows and accepts input until EOF (Ctrl-D)

  • Signals

    • Prompt: Ctrl-C clears line + sets $?=130
    • Ctrl-\ ignored at prompt
    • Children: SIGINTβ†’130, SIGQUITβ†’131
  • Builtins

    • echo, cd, pwd, export, unset, env, exit
    • Builtins run in parent unless piped
    • cd - prints new cwd and updates PWD/OLDPWD
    • export without args prints sorted declare -x ...
    • Invalid export identifiers β†’ error + status 1
    • unset removes keys (and from export_noval)
  • Expansion

    • $VAR, $? with quote rules
    • Adjacent tokens concatenate properly
  • Redirections

    • <, >, >>, << working
    • Heredoc quoted delimiter β†’ no expansion
    • Heredoc Ctrl-C cancels (status 130)
  • Pipelines

    • N-stage pipelines, rightmost exit status applies
    • Redirs applied in the children
  • Path resolution

    • 127 not found; 126 not executable / is directory
  • Norm

    • ≀ 25 lines/function, ≀ 5 functions/file, ≀ 4 parameters/function
    • Indentation, headers, includes, forbidden functions: respected

πŸ‘€ Authors

  • Christian (chrrodri) β€” 42 intra: chrrodri β€” [email protected] β€” GitHub: @kitearuba
  • bsamy β€” 42 intra: bsamy

Educational project for 42 School (Barcelona). Feedback and suggestions are welcome!

About

🐚 42 Minishell β€” evaluated & passed. Norm-compliant shell (mandatory scope + minimal wildcard bonus). Educational reference; not a full Bash.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Contributors 2

  •  
  •