Mini Shell
Build a Unix shell in C that goes beyond running commands: along with pipelines and redirection, it manages jobs like a real shell. Commands can run in the background with &, jobs lists them, fg and bg move them between foreground and background, Ctrl+Z pauses the foreground job, and the shell notices when background jobs finish. It also expands environment variables ($HOME, $?) and handles quoting properly. It's built with care for the details real shells get right: process groups, terminal control, and never leaving zombie processes. You'll learn process groups and sessions, terminal control with tcsetpgrp, signals (SIGCHLD, SIGTSTP and SIGINT), job tables, and robust parsing in C.
- Estimate
- ~14.5h
- Steps
- 7
- Completed by
- 0
- Proposed by
- codeseed.app
Make · POSIX
Project roadmap
- 01
Define the shell's behavior
~1.5hWrite SPEC.md listing what the shell supports: quoting rules (single quotes keep text as is, double quotes allow $VAR), variable expansion ($VAR, ${VAR} and $? for the last status), pipelines, redirection (<, >, >> and 2>), & for background jobs, and the built-ins cd, exit, export, jobs, fg and bg. Include examples with expected output. Set up a Makefile with -Wall -Wextra. Done when: SPEC.md defines every feature with examples.
- 02
Parse commands
~2.5hWrite a tokenizer and parser that turn a line into a job: a list of commands connected by pipes, each with its arguments and redirections, plus a background flag. Handle quotes, escapes with backslash, and variable expansion, and report syntax errors (a pipe with nothing after it, a missing file after >) clearly. Free all memory after each command. Done when: unit tests cover every example in SPEC.md, and valgrind reports no leaks.
- 03
Run pipelines in process groups
~2.5hRun each job's commands with fork, pipe, dup2 and execvp, putting all processes of a job in their own process group (setpgid in both parent and child to avoid a race). For foreground jobs, give the terminal to the job's group with tcsetpgrp and take it back when the job stops or finishes. Done when: pipelines with redirection work, and Ctrl+C stops the whole pipeline but not the shell.
- 04
Add job control
~3hKeep a job table (id, process group, command text and state running, stopped or done). A SIGCHLD handler (or checks with waitpid and WNOHANG before each prompt) updates states, and finished background jobs are reported like [1]+ Done sleep 5. Ctrl+Z stops the foreground job (SIGTSTP, detected with WUNTRACED). Implement jobs, fg %N (continue with SIGCONT in the foreground) and bg %N. Done when: sleep 30, Ctrl+Z, bg, jobs and fg behave like in bash.
- 05
Add built-ins and environment
~1.5hImplement cd (with HOME and OLDPWD), exit (warning when jobs are stopped), and export NAME=value, and make sure built-ins work with redirection where it makes sense (such as jobs > file). Show the current directory in the prompt. Done when: the built-ins work, and exported variables reach child programs.
- 06
Clean up and harden
~1.5hMake sure no zombie processes remain (check with ps), all unused file descriptors are closed in children, and the shell restores the terminal when it exits. Handle interrupted system calls (EINTR) in read and waitpid. Done when: long sessions with many background jobs leave no zombies or leaked descriptors.
- 07
Test it
~2hWrite a test script that runs command scripts through your shell and compares the output with bash for parsing, expansion, pipelines and redirection. Test job control by hand following a checklist in TESTING.md (background jobs, Ctrl+Z, fg and bg, and Done messages). Run everything under valgrind. Done when: the automatic comparisons match, and every item in the checklist works.
Ready to build this?
Get a GitHub repo and start building. Your AI reviewer checks each step as you go.
Tech stack