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