A command-line shell written in C for ICCS227 (Operating Systems), built up over seven milestones. It reads commands, runs external programs, redirects input and output, and manages background jobs with signal-based job control.
___
-------------------- __/_ `. .-"""-.
| Welcome to IC Shell! | \_,` | \-' / )`-')
-------------------- """) `"` \ ((`"`
___Y , .'7 /|
(_,___/...-` (_/_/
- Runs external commands with
fork+execvp, and waits on the foreground job. - Two builtins:
echoandexit [code](the exit code is masked to 8 bits, as the assignment asks).echo $?prints the exit status of the last command. - Repeats the previous command with
!!. - Redirects input with
< fileand output with> file(the output file is created or truncated, mode 0644). Redirection applies to builtins too, then the original stdin/stdout are restored. - Runs a command in the background with a trailing
&and tracks it as a job. - Job control:
jobslists jobs by id (Running / Stopped / Done),fg %nbrings a job to the foreground,bg %nresumes a stopped job in the background. - Forwards Ctrl-C (SIGINT) and Ctrl-Z (SIGTSTP) to the foreground job only, so they don't kill the shell. A SIGCHLD handler reaps children and prints when a background job stops, resumes, or finishes.
- Ctrl-D at the prompt exits.
You need gcc and make on a POSIX system (Linux, WSL, or macOS).
make # builds ./icsh
./icsh # interactive mode
./icsh test.sh # run a script file
make clean
| Command | What it does |
|---|---|
echo <text> |
Print text |
echo $? |
Print the last exit status |
exit [code] |
Leave the shell (code masked to 0–255) |
!! |
Run the previous command again |
<cmd> & |
Run in the background |
jobs |
List background and stopped jobs |
fg %n |
Move job n to the foreground |
bg %n |
Resume stopped job n in the background |
<cmd> < in > out |
Redirect input and/or output |
| anything else | Run it as an external program |
Given a file argument, the shell reads it line by line with no prompt or welcome
banner. Lines that start with # or // are treated as comments. test.sh and
test2.sh are small examples. test.sh is:
echo hello
echo world
!!
exit 5
| File | Responsibility |
|---|---|
icsh.c |
The read-eval loop: prompt, builtins, redirection dispatch, forking external commands, fg/bg |
parser.c |
Tokenizes a line, pulls out < / > / &, and applies or restores redirection with dup2 |
jobs.c |
The job list, kept as a linked list: add, remove, find, update status, print |
signals.c |
Handlers for SIGINT, SIGTSTP, and SIGCHLD |
shell.h |
The job struct, the status enum, and shared prototypes |
Each job runs in its own process group (setpgid), and the foreground job owns
the terminal through tcsetpgrp. That is what lets Ctrl-C and Ctrl-Z land on the
right process instead of the shell itself.
- A command line is capped at 255 characters (
MAX_CMD_BUFFER). - No pipes (
|), no quoting, no globbing, no environment-variable expansion. One input and one output redirection per command. - It uses POSIX job-control calls, so it builds and runs on Linux, WSL, and macOS, but not on native Windows.