DIY Shell within 100 lines


After reading this tutorial, you will be able to

  • understand the essential components of a shell
  • learn basic C programming, including spawn a new child process to execute a command

1. Loop

void loop() {
  int status;
  char* line;
  char** args;
  do {
    printf(">");
    line = readline();
    args = parseline(line);
    status = execute(args);
    free(line);
    free(args);
  } while (status);
}

int main(int argc, char** argv) {
  loop();
  return EXIT_SUCCESS;
}

2. Read

char *readline() {
  char *line = NULL;
  size_t linecap = 0;
  while (getline(&line, &linecap, stdin) == -1) {
    if (feof(stdin)) {
      exit(EXIT_SUCCESS);
    } else {
      perror("readline");
      exit(EXIT_FAILURE);
    }
  }
  return line;
}

3. Parse

#define TOKEN_BUF_SIZE 64
#define TOK_DELIM " \t\r\n\a"

void validateTokens(char *const *tokens) {
  if (tokens == NULL) {
    fprintf(stderr, "allocation error\n");
    exit(EXIT_FAILURE);
  }
}

char **parseline(char *line) {
  int buf_size = TOKEN_BUF_SIZE;
  char** tokens = malloc(buf_size * sizeof(char *));
  validateTokens(tokens);

  char *token;
  int position = 0;
  token = strtok(line, TOK_DELIM);
  while (token != NULL) {
    tokens[position++] = token;
    token = strtok(NULL, TOK_DELIM);
    if (position >= buf_size) {
      buf_size += TOKEN_BUF_SIZE;
      tokens = realloc(tokens, buf_size * sizeof(char *));
      validateTokens(tokens);
    }
  }

  tokens[position] = NULL;
  return tokens;
}

4. Execute

To start a process on Unix, there are 2 ways: being the Init process duplicate a process by using fork().

Init Process
When a Unix computer boots, its kernel is loaded. Once it is loaded and initialized, the kernel starts only one process, which is called Init. This process runs for the entire length of time that the computer is on, and it manages loading up the rest of the processes that you need for your computer to be useful.

fork()
fork() is a system call. When this function is called, the operating system makes a duplicate of the process and starts them both running. The original process is called the “parent”, and the new one is called the “child”. fork() returns 0 to the child process, and it returns to the parent the process ID number (PID) of its child.

int execute(char** args) {
  pid_t pid = fork();
  int status;
  if (pid == 0) {
    if (execvp(args[0], args) == -1) {
      perror("xsh");
    }
    exit(EXIT_FAILURE);
  } else if (pid < 0) {
    perror("xsh 2");
  } else {
    do {
      waitpid(pid, &status, WUNTRACED);
    } while (!WIFEXITED(status) && !WIFSIGNALED(status));
  }
  return EXIT_FAILURE;
}

5. Reference

https://brennan.io/2015/01/16/write-a-shell-in-c/


Author: Shane Tsui
Reprint policy: All articles in this blog are used except for special statements CC BY 4.0 reprint polocy. If reproduced, please indicate source Shane Tsui !

  TOC