UP | HOME

Building a Shell
Systems Programming
COP-3402

Command-line interpreter (shell)

  • Executes commands

    ls ./
    

Commands

  • String input
  • Program name and arguments, e.g., ls ./
  • Built-in commands, e.g., cd or exit

Command loop

  • Prompt for another command after execution

    do
      command = read(userinput)
      interpret(command)
    while command != exit
    

Processing commands

Algorithm

  • Break up command into its parts
  • Create new process
  • Execute new command
  • Wait for command to finish

Diagram

  • ls ./
    • Boxes around tokens
    • Fork a new process
    • execvp ls with ./ as args

Command and arguments

  • Tokenize whitespace-delimited inputs
  • (Diagram)

    ls ./ ../
    

In reality, bash and other command-line interpreters process a full programming language that has structured constructs. We will look at parsing when we talk about compilers.

Using strtok

#include <stdio.h>
#include <inttypes.h>  // intmax_t
#include <string.h>    // strcspn(), strtok
#include <stdlib.h>    // exit()

int main() {
  const int BUFSIZE = 1024;
  char *prog;
  char *arg;
  char *buffer;
  while (1) {
    buffer = malloc(sizeof(char) * BUFSIZE);

    printf("$ ");
    // if you don't flush the buffer, it gets printed out in child as well apparently!

    // read in command with maximum size
    if (!fgets(buffer, BUFSIZE, stdin)) {
      // use ctrl-d to end the input
      printf("\nno more input\n");
      exit(EXIT_SUCCESS);
    }
                printf("length: %" PRIdMAX "\n", strlen(buffer));
                printf("buffer: %s\n", buffer);

    // remove newline
    buffer[strcspn(buffer, "\n")] = '\0';

    if (strlen(buffer) == 0) {
      continue;
    }

    prog = strtok(buffer, " ");
    if (NULL == prog) {
      fprintf(stderr, "empty program name\n");
      break;
      /* exit(EXIT_FAILURE); */
    }

    printf("program name: %s\n", prog);

    while (NULL != (arg = strtok(NULL, " "))) {
      printf("arg: %s\n", arg);
    }
  }
}

Executing programs

What syscalls can we use for this?

fork and exec

execvp.c

#include <stdio.h>
#include <unistd.h>    // fork(), execvp()
#include <stdlib.h>    // exit()
#include <inttypes.h>  // intmax_t

// man 2 execvp

int main(int argc, char **argv) {
  char *prog = "ls";
  const int MAXARGV = 32 + 1 + 1;  // including the NULL terminator and the progname
  char **newargv = malloc(sizeof(char *) * (MAXARGV));
  newargv[0] = prog;
  newargv[1] = "./";
  newargv[2] = "../";
  newargv[3] = NULL;  // NULL-terminate the argv list
  execvp(prog, newargv);
}

Processing pipes

Algorithm

  • Break up command by pipes
  • Create pipe for each pipe symbol
  • For each pipe component
    • Break up command into its parts
    • Create new process
    • Redirect I/O
    • Execute new command
  • Wait for all commands to finish

Diagram

  • ls ./ | wc -l
    • Boxes around tokens
    • Create pipe
    • Fork new processes
    • Redirect i/o
    • execvp ls with ./ as args for each
    • Wait

Author: Paul Gazzillo

Created: 2024-10-01 Tue 03:03

Validate