Building a Shell

By Andrew Healey | ★★★★★ 5 Stars

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

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

Original Article →