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.,
cdorexit
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