Introduction
After three months of dedicated work in 42 Warsaw, I have successfully completed the minishell project. This project involved creating a copy of Bash
in pure C, which has been both challenging and rewarding. In this article, I will share my experiences, challenges faced, and the skills I acquired during this journey.
minishell$ echo line1 > a && echo line2 >> a && < a cat | cat | cat > b
minishell$ cat b
line1
line2
minishell$ >/dev/null 2>&1 cat /etc/passwd | sort | tee >(grep root >&2) | less <&0
Project Overview
The minishell project required me to implement a functional shell that could interpret and execute commands, handle environment variables, manage processes, pipes, redirections and all sort of things that should work in Bash
.
The project can be divided into several key components:
- Command Parsing: A parser that could read user input, tokenize it, and handle various command structures, including built-in commands and external programs.
- Execution: Implementing the logic to execute commands, manage child processes, and handle input/output redirection.
- Built-in Commands: Creating built-in commands:
cd
,echo
,env
,exit
,export
,pwd
, andunset
. - Heredoc: Multi-line input redirection.
- Signal Handling: Managing signals to ensure the shell behaves correctly in response to user interrupts and other signals.
- Environment Variables: Handling environment variables, including setting, unsetting, and exporting them.
Challenges Faced
The journey was not without its challenges. Some of the most significant hurdles included:
- Structure: Designing such robust project in C, a language that lacks many high-level abstractions, was a significant challenge. I had to carefully plan the architecture to ensure maintainability and scalability.
Don't be shy to create data types, structures and so on. It will save you a lot of time in the future. - Memory Management: C requires manual memory management, and in case of shell you must have fully dynamic and scalable memory handling. It had to be perfect, freeing after each command, on any error, exit and so on.
- Inner Workings of Shells: Understanding how shells work under the hood, no, really understanding it - from process creation, signal handling, file descriptor & process management, to redirections and pipes - was crucial. This knowledge was essential for implementing the required features correctly.
# this should work just fine >/dev/null 2>&1 cat /etc/passwd | sort | tee >(grep root >&2) | less <&0
Feelings and Takeaways
Completing the minishell project has been an incredibly rewarding experience. It has not only enhanced my programming skills in C but also really deepened my understanding of unix systems and how shells operate. Also, I get now why people say "In order to know how to use something, you must first know how it works - or even better - build it yourself".
minishell$ find . -name "*.[ch]" -print0 | xargs -0 cat | wc -l
5431 # lines of code
5400 lines of code in pure C, marbled to perfection. Based on our own libc
implementation, zero leaks, zero errors, zero warnings, 100% norm compliance.
I am proud of what I have accomplished and, would be happy not to do it again. At least not in C.