English

Building a minimal shell in C

Why write a shell?

Writing a shell from scratch is one of the clearest ways to understand how Unix works underneath. You get to touch fork, exec, waitpid, and signal handling — all in one small project.

The core loop

Every shell is just this:

while (1) {
    char *line = read_line();
    char **args = parse_line(line);
    execute(args);
    free(line);
    free(args);
}

Read, parse, execute. That’s it.

Forking

The tricky part is that exec replaces the current process image. So you need to fork first, run the command in the child, and wait in the parent.

pid_t pid = fork();
if (pid == 0) {
    execvp(args[0], args);
    perror("exec failed");
    exit(1);
} else {
    waitpid(pid, NULL, 0);
}

Signal handling

A minimal shell should at least handle SIGINT (Ctrl+C) gracefully — ignoring it in the shell process but letting child processes receive it normally.

signal(SIGINT, SIG_IGN);
// After fork, in child:
signal(SIGINT, SIG_DFL);

What’s next

From here you can add: pipes, redirections, builtins like cd and exit, job control, and history. Each feature is a well-scoped problem.