MiniShell is a small Unix shell written in C. It reads a command, starts programs, waits for foreground work, and returns to its prompt. The project is intended to make a few important operating-system ideas visible in code: process creation, signals, pipes, exit codes, and child-process cleanup.
This is a learning shell. Its parser handles ordinary words, single and double
quotes, a small set of $ variables, pipelines, and a trailing &. It does
not implement every feature or every built-in found in Bash.
Build on Linux or another POSIX environment with a C compiler and make:
make
./mshRemove the compiled program with make clean.
pwd
cd /tmp
echo "The shell PID is $$"
echo "The shell executable is $SHELL"
false
echo "The previous status was $?"
ls | wc -l
sleep 20 &
jobsThe default prompt is msh> . Set another prompt by entering an assignment
with no spaces around =:
PS1=mini>PS1 = mini is treated as a command, not a prompt assignment. The parser
supports a trailing & for background commands. Use Ctrl-C to interrupt a
foreground job and Ctrl-Z to stop it. fg resumes a job in the foreground;
bg resumes it in the background. Both commands use the most recent job when
no job number or process-group ID is supplied.
Implemented shell built-ins are cd, pwd, echo, exit, jobs, fg, and
bg. Other command names are searched for in PATH and run as external
programs.
The project keeps its implementation in one C file so the flow is easy to
follow, with constants and shared declarations in main.h.
main() prepares the SHELL variable and prompt, then repeats these steps:
- Reap any finished background children.
- Print the prompt and read a line (
scan_input). - Parse and execute that line (
process_line).
An empty line is ignored. A line beginning with PS1= changes the prompt
inside msh; the = must immediately follow PS1.
tokenize() turns the input into a list of words and operators. For example:
cat notes.txt | wc -l &
becomes roughly cat, notes.txt, |, wc, -l, &. Quotes keep spaces in
one word. $?, $$, $SHELL, and environment variables are expanded while
words are read. process_line() groups the words around each | into pipeline
stages.
Some commands must change msh itself. For example, if cd ran only in a child
process, that child's directory would change while the shell stayed where it
was. builtin() therefore runs a standalone built-in in the shell process.
For a pipeline such as pwd | wc -c, the built-in runs in a child because its
output belongs in the pipe.
External commands use two system calls:
fork()creates a child process.execvp()replaces that child with the requested program, searchingPATH.
The parent shell uses waitpid() to wait for a foreground command and to
collect its exit status. A normal exit status is the program's return value; a
signal exit is represented as 128 + signal number.
run_pipeline() creates a pipe between each pair of stages. A pipe has a read
end and a write end. The shell connects a stage's standard output to the next
stage's standard input with dup2(), then closes the extra file descriptors.
Each stage is a separate child process.
Interactive shells and their foreground jobs use different process groups. The terminal sends Ctrl-C (SIGINT) or Ctrl-Z (SIGTSTP) to the foreground process group. The shell gives the terminal to a foreground job while it runs, then takes terminal control back after the job exits or stops. Background jobs do not receive terminal input by default.
The shell ignores keyboard job-control signals so Ctrl-C does not kill msh.
Children restore the default signal behavior before running a command. The
SIGCHLD handler only sets a flag; reap_children() calls waitpid() later in
the main loop. This keeps non-signal-safe work out of the signal handler and
prevents completed background processes from remaining zombies.
Each job records its process-group ID, child PIDs, command text, and child
states (running, stopped, or complete). jobs displays the table. fg gives a
job the terminal and waits for it. bg sends SIGCONT and leaves the job in the
background. When every process in a background job finishes, msh prints its
exit status and removes the job entry.
main.c— input loop, parser, built-ins, process launching, and job control.main.h— shared constants and function declarations.Makefile— build and clean targets.README.md— this guide.
This project is intentionally smaller than a production shell. It does not
support redirection (>, <), command separators (;), shell scripting,
globbing, command substitution, or the full Bash built-in list. Variable
expansion is deliberately limited and does not perform shell word splitting.