A Unix shell implemented in C, built as a semester-long university systems programming project. TinyShell supports command execution, I/O redirection, pipelines, and full job control — developed incrementally across three phases.
- Interactive prompt with
fgets-based input reading - Custom tokenizer supporting single and double quotes
PATH-aware executable resolution (find_executable)fork/execvexecution model with proper exit-status reporting- Built-in
exitcommand andEOF(Ctrl+D) handling
| Operator | Behaviour |
|---|---|
< |
Redirect stdin from file |
> |
Redirect stdout to file (truncate) |
>> |
Redirect stdout to file (append) |
2> |
Redirect stderr to file |
| |
Pipe stdout of one command to stdin of the next |
- Supports multiple pipes (
cmd1 | cmd2 | cmd3 | ..., up to 16 commands) - Redirections and pipes can be freely combined
- Background execution with
& - Signal handling
Ctrl-C(SIGINT): terminates the foreground job; shell ignores itCtrl-Z(SIGTSTP): stops the foreground job and adds it to the job listSIGCHLD: automatically reaps zombie processes
- Built-in job management commands
| Command | Description |
|---|---|
jobs |
List all jobs and their status (Running / Stopped / Done) |
fg [%n] |
Bring job to foreground |
bg [%n] |
Resume stopped job in background |
- Job specifiers:
%n,%+,%-,%% - Race condition prevention via SIGCHLD blocking during job list updates
- Process groups for proper signal isolation (each job gets its own PGID)
- Terminal control properly transferred between shell and foreground jobs
.
├── tinyshell.c # Main shell loop, built-ins, fork/exec logic
├── utilities.c # Parser, redirections, pipeline, job control implementation
├── utilities.h # Structs, constants, function declarations
└── Makefile # Build configuration
Command — holds parsed redirection targets for a single command:
typedef struct {
char *inputFile; // <
char *outputFile; // >
char *appendFile; // >>
char *errorFile; // 2>
int appendMode;
} Command;job_t — linked-list node representing a background/stopped job:
typedef struct job {
int jobId;
pid_t pgid;
char command[BUFFER_SIZE];
JobState state; // RUNNING | STOPPED | DONE
struct job *next;
} job_t;A C compiler (gcc) and make. On Debian/Ubuntu or WSL:
sudo apt update && sudo apt install build-essential
gcc --version # verify installationmake all./tinyshell# Basic commands
tinyshell> ls -la
tinyshell> pwd
# I/O Redirection
tinyshell> ls > output.txt
tinyshell> sort < input.txt >> sorted.txt
tinyshell> make 2> errors.log
# Pipelines
tinyshell> ls -la | grep ".c" | wc -l
tinyshell> cat file.txt | sort | uniq
# Background execution
tinyshell> sleep 10 &
[1] 4821
# Job control
tinyshell> jobs
[1] RUNNING sleep 10
tinyshell> fg %1
tinyshell> bg %1
# Quoted arguments
tinyshell> echo "Hello, World!"
tinyshell> grep 'search term' file.txt- The shell ignores
SIGINT,SIGTSTP,SIGTTIN, andSIGTTOU; child processes reset these toSIG_DFL. SIGCHLDis blocked duringfork()and job-list mutations to prevent race conditions.find_executablehandles both absolute/relative paths (containing/) andPATH-resolution; the returned string is heap-allocated and must be freed by the caller.splitCommandsis quote-aware, so pipes inside quoted strings are not treated as delimiters.
Developed and tested on Linux (Ubuntu 24 / WSL2). Not tested on macOS — __strtok_r is used internally and may require adjustment on non-GNU platforms.