My own implementation of a lightweight POSIX-style shell in Python, written to better understand how a shell works under the hood.
This project is part of the CodeCrafters "Build Your Own Shell" challenge, where you incrementally build a shell capable of parsing commands, running external programs, and supporting builtin commands like cd, pwd, and echo.
- Interactive REPL (Read-Evaluate-Parse Loop) with a
$prompt - Builtin commands:
echo,exit,type,cdandpwd - External command execution via
PATHlookup - Command-not-found error handling
- Navigation;
cdandpwdbuiltins
This shell is being built stage-by-stage. Upcoming work includes:
- Quoting; preserve whitespace and special characters
- Redirection; Standard output and error redirection (
>,>>, etc.) - Command Completion; autocomplete commands and executable files
- Filename Completion
- Programmable Completion;
completebuiltin, custom tab-completion behaviour - Background Jobs; multiple commands in the background at once
- Pipelines (
|) - History;
historybuiltin - History Persistence; save history to a file
- Parameter Expansion; shell variables
- How a REPL reads input, parses commands, and dispatches to builtins vs external programs
- How shells resolve executables using the
PATHenvironment variable - The difference between shell builtins and programs found on disk
This project uses uv as its package manager. Install uv if you don't have it yet:
curl -LsSf https://astral.sh/uv/install.sh | shClone the repository:
git clone https://github.com/Maciekm1/codecrafters-shell-python
cd codecrafters-shell-pythonInstall dependencies (uv reads pyproject.toml and manages the Python version via .python-version):
uv syncStart the shell:
./your_program.shYou should see a $ prompt. Try a few commands:
$ echo hello world
hello world
$ type echo
echo is a shell builtin
$ ls -l
<file listing>
$ exit
The entry point for the shell implementation is app/main.py. Locally, your_program.sh runs it via uv run.
# app/main.py — simplified flow
while True:
user_input = read_line("$ ")
handle_command(user_input) # dispatches to builtins or subprocessExample shell session:
$ echo hello
hello
$ type ls
ls is /usr/bin/ls
$ unknown_command
unknown_command: command not found
$ exitTo run tests on CodeCrafters servers:
codecrafters submit