A zero-configuration POSIX command-line shell engineered in pure C, featuring dynamic multi-stage execution pipelines and persistent background job tracking.
Built as part of a self-directed C curriculum.
Requires gcc and make. Once the repository is cloned from the remote, you can run these build commands:
runtime builds:
make setup_deps # Clone the dependency repos
make # Compiles the executable cshell binarydev builds:
make test # Compiles and runs the test suite (./tests)
make debug # Builds with debug symbols and sanitisersclean:
make clean_deps # Cleans compiled binaries, .o, .a, and .d files (including compiled dependencies)
make clean # Cleans compiled binaries, .o, and .d files (just for cshell)include/cshell/ # Public interface definitions
├── test_framework.h # Basic test framework macros
├── executor.h # Pipeline execution, forking, and descriptor hygiene
├── expansion.h # Environment variable token identification and translation
├── parser.h # Lexical analysis and Pipeline/Command AST generation
├── prompt.h # Context-aware path formatting and ANSI escape logic
└── tracker.h # Persistent background job tables and state interfaces
src/ # Core implementation (.c files)
tests/ # Per-module unit tests
To prevent subshell forks from discarding state mutations, cshell routes environment and directory manipulations directly to internal system calls within the parent process context.
cd [path]: Updates the current working directory of the shell viachdir(). Supports environment variables (e.g., cd $HOME).export KEY=VALUE: Assigns or modifies environment variables in the process environment block viasetenv().exit(orCTRL+D): Cleanly breaks the REPL control loop and terminates the shell.
The prompt dynamically evaluates the user's workspace on every REPL iteration. To maintain screen real estate, it detects the HOME environment variable prefix and cleanly truncates it to a ~ shorthand while isolating the path visually using high-contrast ANSI colour-coding
cshell:/mnt/c/Users> cd $HOME
cshell:~> cd ./projects/cshell
cshell:~/projects/cshell>Note: The ANSI colour-coding is not possible to convey in this documentation. If you run the
cshellbinary, you will see that the path (between:and>) is in pink and the rest is in default terminal colours (white for me).
cshell orchestrates arbitrary N-stage pipelines. It distinguishes between isolated subshell execution and parent-process evaluation: single built-in commands run in-place to mutate the parent environment, whereas pipelined built-ins automatically cascade into isolated child subshells to protect parent state.
cshell:~> echo systems_programming | grep -o programming
programmingExternal process execution safely handles low-level input (<) and output (>) file descriptor redirection, routing byte streams seamlessly across disk boundaries.
cshell:~> echo hello > source.txt # write "hello" to source.txt
cshell:~> cat < source.txt > destination.txt # "hello" copies to destination.txtTrailing & operators intercept parsing boundaries to launch non-blocking background pipelines. Job lifetimes are managed natively by a dedicated tracking engine that prints clean, lifecycle status updates immediately before rendering the next prompt.
cshell:~> sleep 10 &
[1] 11754
cshell:~> # wait 10 seconds
[1]+ Done sleep 10 &Tokens prefixed with $ are scanned, extracted, and resolved via process environment blocks at runtime prior to pipeline execution routing, ensuring universal variable availability across both external binaries and internal built-ins.
As discussed in the Native Built-in Commands section, it is possible to create environment variables through export KEY=VALUE.
cshell:~> export PROJECT_DIR=/tmp/test
cshell:~> cd $PROJECT_DIR
cshell:/tmp/test>The design of cshell centers around absolute memory determinism and safe systems orchestration without third-party library wrappers.
To eliminate heap fragmentation, tracking overhead, and complex pointer bookkeeping, memory requested during tokenisation and pipeline generation is bound to a single execution epoch. Once a pipeline completes its execution lifecycle, the arena offset is reset to zero in a single, deterministic O(1) operation, bypassing individual node destruction loops.
The multi-stage execution engine drives an N-stage rolling file descriptor allocation loop. Instead of generating a massive global matrix of pipes ahead of time, standard descriptors are created and rotated progressively. Child contexts copy active boundaries via dup2, while rigorous parent-process descriptor hygiene explicitly closes unused write ends to guarantee that pipelines never deadlock or leak descriptors.
cshell utilises signal blocking barriers around critical process forks and transferring process harvesting ownership entirely to a synchronous tracking engine. Active background jobs are monitored via non-blocking waitpid(..., WNOHANG) inquiries executed strictly at the top of the REPL loop, preserving the SA_RESTART integrity of your user input streams.
Created by WillEdgington