WeCodeAcademy

Step 4: Run programs with fork and exec

To run another program the shell uses a classic Unix trick. fork makes a copy of the shell (the child). In the child, exec replaces the copy with the new program. The parent calls waitpid and learns the child's exit status: 0 means success, anything else is a failure (127 by convention means "command not found"). Note the fflush(stdout) before fork: without it, text waiting in the output buffer would be duplicated into the child and printed twice.

ExampleC
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <unistd.h>

#define MAX_ARGS 16

int tokenize(char *line, char *argv[]) {
  int argc = 0;
  char *p = line;
  while (*p != '\0' && argc < MAX_ARGS - 1) {
    while (*p == ' ') p++;
    if (*p == '\0') break;
    argv[argc++] = p;
    while (*p != '\0' && *p != ' ') p++;
    if (*p != '\0') *p++ = '\0';
  }
  argv[argc] = NULL;
  return argc;
}

// Run a program: fork a child, replace it with the program (exec), and wait for it to finish.
int run_external(char *argv[]) {
  fflush(stdout);                      // otherwise buffered text would be printed twice
  pid_t pid = fork();
  if (pid < 0) {
    perror("fork");
    return 1;
  }
  if (pid == 0) {                      // the child
    execvp(argv[0], argv);
    printf("tiny: command not found: %s\n", argv[0]);
    fflush(stdout);
    _exit(127);                        // exec only returns if it failed
  }
  int status;
  waitpid(pid, &status, 0);            // the parent waits
  return WIFEXITED(status) ? WEXITSTATUS(status) : 1;
}

int main(void) {
  char *script[] = { "echo hello from a child process", "true", "false", "nosuchprogram --flag", "echo one two three" };
  char *argv[MAX_ARGS];
  for (int i = 0; i < 5; i++) {
    char line[100];
    strcpy(line, script[i]);
    printf("tiny> %s\n", line);
    int argc = tokenize(line, argv);
    if (argc == 0) continue;
    int status = run_external(argv);
    printf("[exit status %d]\n", status);
  }
  return 0;
}
Output
tiny> echo hello from a child process
hello from a child process
[exit status 0]
tiny> true
[exit status 0]
tiny> false
[exit status 1]
tiny> nosuchprogram --flag
tiny: command not found: nosuchprogram
[exit status 127]
tiny> echo one two three
one two three
[exit status 0]

true exits 0 and false exits 1, which is exactly what scripts test with if. A program that does not exist makes execvp return, so the child prints a message and exits with 127.

Quick check

What does a process exit status of 0 mean?

Finish the quick check (and any code exercise) to complete this lesson, or mark it done yourself.

Tip: use the left and right arrow keys to move between lessons.