Build Your Own Unix Shell in C: A Step‑by‑Step Tutorial
This tutorial walks you through creating a simple Unix shell in C, covering its lifecycle, input handling, command parsing, process launching, built‑in commands, and full source compilation, while providing complete code examples and explanations.
Many programmers doubt they are "real" developers, but building a simple Unix shell in C (named lsh) is an excellent way to prove otherwise; the full source is available on GitHub.
Shell Basic Lifecycle
A shell typically performs three stages:
Initialization : load configuration files (the tutorial’s shell skips this).
Read‑execute : read commands from standard input, parse them, and execute.
Termination : clean up memory and exit.
Main Function
int main(int argc, char **argv) {
// If a config file existed we would load it here.
lsh_loop();
return EXIT_SUCCESS;
}Read a Line
Reading an arbitrary‑length line from stdin requires dynamic buffer allocation. The implementation grows the buffer as needed and returns a null‑terminated string.
#define LSH_RL_BUFSIZE 1024
char *lsh_read_line(void) {
int bufsize = LSH_RL_BUFSIZE;
int position = 0;
char *buffer = malloc(sizeof(char) * bufsize);
int c;
if (!buffer) {
fprintf(stderr, "lsh: allocation error
");
exit(EXIT_FAILURE);
}
while (1) {
c = getchar();
if (c == EOF || c == '
') {
buffer[position] = '\0';
return buffer;
} else {
buffer[position] = c;
}
position++;
if (position >= bufsize) {
bufsize += LSH_RL_BUFSIZE;
buffer = realloc(buffer, bufsize);
if (!buffer) {
fprintf(stderr, "lsh: allocation error
");
exit(EXIT_FAILURE);
}
}
}
}Parse a Line
The line is split into tokens using whitespace as delimiters. This simple parser does not handle quotes or escape characters.
#define LSH_TOK_BUFSIZE 64
#define LSH_TOK_DELIM " \t
\a"
char **lsh_split_line(char *line) {
int bufsize = LSH_TOK_BUFSIZE, position = 0;
char **tokens = malloc(bufsize * sizeof(char*));
char *token;
if (!tokens) {
fprintf(stderr, "lsh: allocation error
");
exit(EXIT_FAILURE);
}
token = strtok(line, LSH_TOK_DELIM);
while (token != NULL) {
tokens[position] = token;
position++;
if (position >= bufsize) {
bufsize += LSH_TOK_BUFSIZE;
tokens = realloc(tokens, bufsize * sizeof(char*));
if (!tokens) {
fprintf(stderr, "lsh: allocation error
");
exit(EXIT_FAILURE);
}
}
token = strtok(NULL, LSH_TOK_DELIM);
}
tokens[position] = NULL;
return tokens;
}Launch a Process
To run external commands the shell forks a child process and uses execvp to replace the child’s image. The parent waits for the child to finish.
int lsh_launch(char **args) {
pid_t pid, wpid;
int status;
pid = fork();
if (pid == 0) { // child
if (execvp(args[0], args) == -1) {
perror("lsh");
}
exit(EXIT_FAILURE);
} else if (pid < 0) { // fork error
perror("lsh");
} else { // parent
do {
wpid = waitpid(pid, &status, WUNTRACED);
} while (!WIFEXITED(status) && !WIFSIGNALED(status));
}
return 1;
}Built‑in Commands
Some commands must be handled by the shell itself because they affect the shell’s own state (e.g., cd, exit, help).
int lsh_cd(char **args) {
if (args[1] == NULL) {
fprintf(stderr, "lsh: expected argument to \"cd\"
");
} else {
if (chdir(args[1]) != 0) {
perror("lsh");
}
}
return 1;
}
int lsh_help(char **args) {
int i;
printf("Stephen Brennan's LSH
");
printf("Type program names and arguments, and hit enter.
");
printf("The following are built in:
");
for (i = 0; i < lsh_num_builtins(); i++) {
printf(" %s
", builtin_str[i]);
}
printf("Use the man command for information on other programs.
");
return 1;
}
int lsh_exit(char **args) {
return 0;
}Execute Command
The executor checks whether the command matches a built‑in; if not, it launches an external process.
int lsh_execute(char **args) {
int i;
if (args[0] == NULL) {
return 1; // empty command
}
for (i = 0; i < lsh_num_builtins(); i++) {
if (strcmp(args[0], builtin_str[i]) == 0) {
return (*builtin_func[i])(args);
}
}
return lsh_launch(args);
}Putting It All Together
The complete shell consists of the loop ( lsh_loop) that repeatedly reads a line, splits it, executes it, and frees allocated memory. Compile with the required headers ( <stdio.h>, <stdlib.h>, <unistd.h>, <sys/wait.h>, <string.h>) and run the resulting binary.
Compilation example:
gcc -o lsh main.c
./lshSource: https://brennan.io/2015/01/16/write-a-shell-in-c/ (original article by "万能的大雄")
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
21CTO
21CTO (21CTO.com) offers developers community, training, and services, making it your go‑to learning and service platform.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
