This project is a custom shell implementation written in C++. It was developed as part of the CodeCrafters "Build Your Own Shell" challenge. The shell mimics POSIX-compliant behavior, supporting built-in commands, external program execution, input/output redirection, and command pipelining.
- Built-in Commands: Support for
cd,pwd,echo,type,exit, andhistory. - External Program Execution: Executes programs found in the system
PATHusingexecvp. - Input Parsing: Handles single quotes (
'), double quotes ("), and backslash escapes (\) using a custom tokenizer. - Redirection: Supports standard output redirection (
>), append mode (>>), and standard error redirection (2>). - Pipelining: Supports chaining multiple commands using the pipe operator (
|). - Autocompletion: Tab completion for executables in the system
PATHutilizing GNU Readline. - History Management: Persists command history across sessions via a file specified by
HISTFILE.
Instead of using simple string splitting or regex, the input parsing logic (tokenize_single_quote) implements a Finite State Machine. This ensures robust handling of quoted strings and escape sequences.
- States: The parser transitions between states such as
STATE_Q0(unquoted),STATE_Q1(single-quoted), andSTATE_Q3(double-quoted). - Behavior:
- Spaces inside quotes are preserved as part of a single argument.
- Escape characters (
\) are processed only within double quotes or unquoted text, but treated literally inside single quotes. - Redirection operators (
>,2>) are detected during this tokenization phase to separate filenames from command arguments.
The shell utilizes the standard UNIX fork-exec pattern:
- Forking: The
fork()system call creates a child process. - Execution: The child process replaces its memory image with the target program using
execvp(). - Waiting: The parent process uses
waitpid()to pause execution until the child process terminates, ensuring the shell does not return to the prompt prematurely.
The execute_pipeline function manages inter-process communication for chained commands (e.g., ls | grep .cpp).
- Pipe Creation: It initializes
N-1pipes forNcommands. - File Descriptor Duplication:
dup2is used to map the write end of a pipe toSTDOUT_FILENOof the current process.dup2maps the read end of the previous pipe toSTDIN_FILENOof the next process.
- Resource Management: Strict care is taken to close unused pipe ends in both parent and child processes to prevent deadlocks and file descriptor leaks.
Redirection is handled immediately after forking but before executing the command.
- The code parses tokens to identify file descriptors (defaulting to 1 for
>and 2 for2>). - It opens the target file with appropriate flags (
O_TRUNCfor overwrite,O_APPENDfor append). dup2replaces the target file descriptor (stdout or stderr) with the file descriptor of the opened file.
The project integrates libreadline to provide a polished user experience.
- Input Handling: Replaces
std::cinwithreadline()to support line editing and history navigation (Up/Down arrows). - Custom Completion: A custom callback
my_completionhooks into readline to scan the systemPATH. It calculates the Longest Common Prefix (LCP) to suggest autocompletions when the user presses Tab.
- C++ Compiler: GCC or Clang with C++17 support (required for
std::filesystem). - Libraries:
libreadline-dev(or equivalent for your distribution). - OS: Linux or macOS (POSIX environment).
To compile the shell, use the following command:
g++ -std=c++17 -o shell main.cpp -lreadline
