Building a Shell
C Systems Programming Tutorial
A deep dive into building a toy shell (andsh) from scratch in C. The shell sits in front of a lot of my work, but I mostly use it for the outcome: running unix commands and scripts.
Key Features Built:
- REPL (Read-Eval-Print Loop)
- Tokenization and argv parsing
- External command execution (fork/exec)
- Built-in commands (cd, exit)
- Environment variable expansion ($HOME, $?)
- Piping (cmd1 | cmd2 | cmd3)
- Tab completion and history (via readline)
1. REPL Structure
// repl.h
typedef struct {
int last_status;
int running;
int interactive;
} Shell;
The shell maintains state including the last command's exit status, whether it's running interactively, and uses the classic read-eval-print loop pattern.
2. Running External Commands
pid = fork();
if (pid == 0) {
execvp(argv[0], argv);
_exit(errno == ENOENT ? 127 : 126);
}
while (waitpid(pid, &status, 0) < 0) {
if (errno != EINTR) { /* handle error */ }
}
Key insight: cd must be a built-in because it needs to change the working directory of the shell process itself, not a child process.
3. Environment Variable Expansion
static char *expand_word(const Shell *shell, const char *word) {
if (strcmp(word, "$?") == 0) {
// Expand to last exit status
snprintf(status, sizeof(status), "%d", shell->last_status);
return strdup(status);
}
// Look up $NAME in environment
}
4. Piping Implementation
A pipe connects stdout of one process to stdin of another. The shell creates N-1 pipes for N commands.
// Connect prev_read to stdin, pipefd[1] to stdout
dup2(prev_read, STDIN_FILENO);
dup2(pipefd[1], STDOUT_FILENO);
5. Tab Completion with readline
rl_attempted_completion_function = shell_completion;
// Scan current directory and $PATH for matches
Demo
andsh$ cd /
andsh$ pwd
/
andsh$ echo $HOME
/Users/andrew
andsh$ printf abc\n | tr a-z A-Z | rev
CBA
andsh$ unam<Tab>
andsh$ uname
andsh$ pwd
/
andsh$ echo $HOME
/Users/andrew
andsh$ printf abc\n | tr a-z A-Z | rev
CBA
andsh$ unam<Tab>
andsh$ uname
What's Missing
- Quoting (echo "hello world")
- Redirection (<, >, >>)
- More builtins
Why This Matters
This is a fantastic learning exercise for understanding:
- How processes work in Unix
- IPC (Inter-Process Communication)
- File descriptors and redirection
- Why some shell commands are built-in