Shells
Processes
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.
Redirection example
Diagram
$ ls >out.txt
Identify the program and redirections.
Piping and redirection
Diagram
ls / | wc
Identify the program, redirections, and piping.
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 fprintf(stderr, "no more input\n"); exit(EXIT_SUCCESS); } fprintf(stderr, "length: %" PRIdMAX "\n", strlen(buffer)); fprintf(stderr, "buffer: %s\n", buffer); // remove newline buffer[strcspn(buffer, "\n")] = '\0'; if (strlen(buffer) == 0) { continue; } char *prog1; char *next_str; char *buffercopy = malloc(sizeof(char) * BUFSIZE); next_str = strtok(strncpy(buffercopy, buffer, BUFSIZE), " "); prog1 = next_str; fprintf(stderr, "%s\n", next_str); while ((next_str = strtok(NULL, " ")) != NULL) { fprintf(stderr, "%s\n", next_str); } fprintf(stderr, "first program name: %s\n", prog1); } }
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
- Tokenize command by whitespace
- Identify the following
- Left and right (if present) pipes
- Arguments (if present) for both programs
- In/out redirections (if present) for both programs
- For each pipe program component
- Create new process
- Redirect I/O (if present)
- Redirect to file for redirect commands if present
- Redirect to/from pipe if present
- Execute new commands
- 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