minishell

🧠 Project Overview

Minishell is a Unix-like shell implemented from scratch as part of the 42 Madrid curriculum by students m-allera and frromero. The primary objective is to recreate the core functionalities of a real shell, such as bash or zsh, using only low-level C system calls and standard libraries β€” without relying on external libraries or shortcuts.

This project is a deep dive into system programming, focusing on how Unix-based shells interpret commands, spawn and manage processes, handle input/output streams, manage memory, and interact with the terminal interface. It’s not just about getting commands to run; it’s about understanding the engine behind the command line.

Working in pairs simulates a real development environment and emphasizes version control, teamwork, and clean code architecture.


πŸš€ Key Functionalities

βœ… Command Parsing and Execution

Minishell interprets and executes both built-in and external commands.

Built-in Commands:

  • echo: Displays arguments to standard output
  • cd: Changes the current working directory
  • pwd: Prints the current directory path
  • export: Sets environment variables
  • unset: Removes environment variables
  • env: Lists current environment variables
  • exit: Exits the shell, optionally with a given status code

These commands are executed internally, bypassing process creation to remain compliant with standard shell behavior and improve performance.

External Commands:

Minishell locates binaries using the PATH environment variable, then forks and executes them via execve(), mimicking how a real shell handles external programs.

ls -la | grep ".c" > output.txt

🧡 Process Management

Process control is at the heart of a shell. Minishell uses:

  • fork() to create child processes
  • execve() to execute external binaries
  • wait() / waitpid() to manage process lifecycles and collect their exit status

Each pipe segment and command execution runs in its own process, requiring precise management of file descriptors, error handling, and resource deallocation.


πŸ”„ Input/Output Redirection and Pipes

The shell supports:

  • Input Redirection (<)
  • Output Redirection (overwrite) (>)
  • Output Redirection (append) (>>)
  • Pipelines (|)

Behind the scenes, this involves duplicating file descriptors using dup2(), setting up communication through pipe(), and carefully orchestrating multiple processes to simulate stream chaining β€” all while preserving isolation between commands.

< input.txt grep "error" | sort | uniq > results.log

🧩 Lexing, Parsing, and Environment Management

A key part of shell development is lexical analysis and parsing:

  • Tokenization: Commands are split based on whitespace and special characters (e.g. |, <, >)
  • Quote Handling: Properly supports single (') and double (") quotes, ensuring argument grouping
  • Variable Expansion: Environment variables like $HOME or $USER are expanded before execution
  • Escape Sequences: Properly handles backslashes and nested expansions

This is all done manually, without using tools like lex, yacc, or readline, requiring the implementation of a custom parser.


🚦 Signal Handling

Minishell handles terminal signals to ensure smooth user interaction:

  • SIGINT (Ctrl+C): Interrupts a running process but not the shell
  • SIGQUIT (Ctrl+\): Suppressed as in modern shells
  • EOF (Ctrl+D): Exits the shell gracefully when received on an empty prompt
  • Custom Prompt Behavior: Ensures that signals don’t break the prompt layout or logic

Signal behavior is carefully controlled using signal() or sigaction() to simulate a real terminal session.


🧼 Memory Management

Minishell adheres to strict memory hygiene:

  • All dynamic memory allocations (malloc) are freed appropriately
  • Avoids memory leaks and double frees
  • Proper management of environment variables in a dynamic array
  • Reinitializes memory and file descriptors for each command cycle

Memory checks are continuously validated using tools like Valgrind or AddressSanitizer.


πŸ“‚ Architecture Summary

The shell is structured in modular components:

Module Purpose
lexer/ Tokenizes input strings
parser/ Builds command structures and handles syntax rules
executor/ Handles forks, redirections, and command execution
builtins/ Built-in command implementations
env/ Environment variable management
signals/ Signal trapping and user interruption handling
utils/ Helper functions (string ops, error printing, etc.)

πŸ” Example Use Cases

minishell$ echo "Welcome to Minishell"
Welcome to Minishell

minishell$ export USER=fran
minishell$ echo $USER
fran

minishell$ cat file.txt | grep "42" | wc -l > count.txt

πŸ§ͺ Testing and Limitations

While Minishell implements many core features, it does not (yet) include:

  • Job control (fg, bg)
  • Background processes (&)
  • Subshells or command substitution ($(...))
  • Wildcards (*)
  • Here-docs (<<)

Each implementation is rigorously tested against expected shell behavior and edge cases, and the project passes all functional criteria defined in the 42 subject.


🏁 Conclusion

Minishell is a comprehensive systems-level project that demonstrates:

  • Deep understanding of Unix internals
  • Mastery of process control and inter-process communication
  • Proficiency in memory and file descriptor management
  • Ability to write robust, modular, and maintainable C code

It bridges the gap between application-level programming and operating system fundamentals β€” an essential milestone in any low-level programming journey.