A small interactive Unix-like shell written in C, with foreground and background execution, a handful of built-in commands, SIGINT handling, and command history. It's a systems-programming exercise: fork/exec, signal handling, and manual memory management, not a drop-in replacement for bash.
- Interactive prompt showing the current working directory
- Foreground and background execution (
&) - Built-ins:
cd,pwd,exit,help,history - History recall:
!!re-runs the last command,!nruns command numbern - SIGINT (Ctrl+C) is caught so the shell doesn't die when you interrupt it
- Zombie process cleanup after each command
- Automated tests via GoogleTest
- C (shell) and C++ (test suite)
- CMake, with GoogleTest pulled in via
FetchContent - Linux / POSIX
.
├── CMakeLists.txt
├── include
│ ├── internal_commands.h
│ └── msgs.h
├── src
│ ├── internal_commands.c
│ └── main.c
└── gtest
└── shell_test.cpp
cmake -S . -B build
cmake --build buildThis builds two targets: build/shell, the shell itself, and build/shell_test, the test binary.
./build/shellThe test suite uses GoogleTest, fetched automatically by CMake. After building, run it through ctest:
cd build
ctest --output-on-failureOr run the test binary directly for more verbose GoogleTest output:
./build/shell_testTo build with AddressSanitizer and LeakSanitizer enabled, configure with ENABLE_ASAN:
cmake -S . -B build-asan -DCMAKE_BUILD_TYPE=Debug -DENABLE_ASAN=ON
cmake --build build-asan
cd build-asan && ctest --output-on-failureThis is also run as a separate job in CI (see below), so any memory errors in the built-ins or history handling get caught before merge.
GitHub Actions builds the project and runs the test suite on every push and pull request to main, in two jobs: a plain build/test, and a second build/test under AddressSanitizer. The workflow is in .github/workflows/ci.yml.
| Command | Description |
|---|---|
pwd |
Print the current working directory |
cd <dir> |
Change directory |
cd or cd ~ |
Change to the home directory |
cd - |
Switch back to the previous directory |
history |
Show the last 10 commands |
!! |
Re-run the last command |
!n |
Run command number n from history |
help |
Show help for a command |
exit |
Exit the shell |
Append & to run a command in the background:
sleep 10 &/home/user$ history
9 history
8 pwd
7 ls
6 sleep 5 &
5 cd /usr
4 pwd
3 ls -l
2 echo Hello
1 whoami
0 pwd
/home/user$
Fork failures, exec failures, bad cd targets, and invalid history references are reported to stderr rather than crashing the shell. SIGINT during a foreground command interrupts it without killing the shell process.
This is a minimal shell, not a POSIX-compliant one. Known gaps:
- No pipes (
|) or I/O redirection (<,>,>>) - No
&&,||, or;to chain multiple commands on one line - No quoting or escaping — arguments are split on whitespace with
strtok_r, so quoted strings and spaces in arguments aren't handled - No environment variable expansion (
$HOME,$PATH, etc.) or globbing (*.txt) - No job control — background jobs aren't tracked, so there's no
jobs,fg, orbg - History holds the last 10 commands only, and commands are limited to 64 whitespace-separated tokens
&is only recognized as its own token at the end of a command, not appended directly to a word (e.g.sleep 10&won't background it)
If you need any of these, you're probably looking for bash or zsh — this project is meant for learning process control and signal handling, not daily use.