Back to Projects

Nov 2025 - Dec 2025

s3 Shell

This was my Software Systems coursework at Imperial. I did it with one other person, Yichan Kim.

s3 is a Unix-like shell we wrote in C. It's essentially just a REPL: you type a line, it parses it, and it runs the program with fork() and execvp(). We don't reimplement ls or cat or grep. We find the binary on PATH and replace a child process with it. Then we kept adding features on top of that: redirection, cd, pipes, batches, then subshells, including nested ones. The prompt is just the working directory, so it looks like [/home/user/txt s3]$.

The order you check operators in actually matters. We look for ; first, then cd, then pipes, then redirection, then subshells, then a normal command. If you do cd before ;, then cd txt ; ls gets treated as one cd with too many arguments. cd also has to run in the parent. If you chdir() in a child, the directory change dies with it and the prompt never moves. Everything else is fork, exec, wait.

Redirection is one operator per command. The child opens the file, calls dup2() onto stdin or stdout, then execs. Do that in the parent and you've just redirected the shell itself. Pipes are the same idea across processes: stdout of one stage becomes stdin of the next, and you have to close the write end in the parent or the next process never sees EOF. We had a bug for a while where cat file | sort | tac > output.txt was passing > to tac as an argument. ; just runs commands one after another. Subshells fork a separate process, so cd inside ( ) doesn't move the parent. Nested ones work the same way: peel the outer parentheses and run the inside again.

We didn't do two redirections on one command, cd ~, job control, history, or globbing. Quotes also aren't fully respected, so echo "a|b" can still get treated as a pipe. Standalone subshells exec ./s3, so the binary needs to be in the current directory.

Reflections

A shell is mostly file descriptors and who is allowed to call chdir. fork and execvp are the easy bit. The actual work is not leaking pipe ends, not treating operators inside parentheses as separators, and not redirecting the shell itself.

If I did it again I'd scan operators with quote awareness so echo "a|b" stays one argument, and I wouldn't make standalone subshells depend on ./s3 sitting in the current directory. Job control would have been the fun extension, but that's a different project.

Seeing pwd ; (cd txt ; pwd) ; pwd print two different directories was the moment it started feeling like a real shell.

CUnix