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
orexit
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