# Recitation 8
This Recitation explores the implementation of a basic command-line shell in C. The shell supports different modes of operation (batch and interactive), executes built-in and external commands, handles I/O redirection, wildcards, and pipes.
## batch_mode or interactive_mode
The main function is the starting point of the shell. It checks the command-line arguments to determine the mode of operation:
- **Batch Mode**: Activated when exactly one argument is provided. Processes commands from a file.
- **Interactive Mode**: Default mode for executing commands entered by the user.
```c
int main(int argc, char *argv[]) {
if (argc == 2) {
// batch_mode
batch_mode(argv[1]);
} else {
// interactive_mode first
interactive_mode();
}
return 0;
}
```
### batch_mode
In batch mode, the shell reads commands from a specified file and executes each line as a command:
- Opens the file and reads commands line by line.
- Calls `parse_and_execute` for each command.
- Handles file-related errors and closes the file after processing.
```c
void batch_mode(const char *filename) {
char command[MAX_COMMAND_LENGTH];
FILE *file = fopen(filename, "r");
if (!file) {
perror("open file error");
exit(EXIT_FAILURE);
}
while (fgets(command, MAX_COMMAND_LENGTH, file)) {
command[strcspn(command, "\n")] = 0;
parse_and_execute(command);
}
fclose(file);
}
```
### interactive_mode
In interactive mode, the shell prompts the user to enter commands:
- Reads commands from standard input.
- Processes each command until an "exit" command is entered or an error occurs.
- Uses `parse_and_execute` to handle each command.
```c
void interactive_mode() {
char command[MAX_COMMAND_LENGTH];
printf("welcome to the shell!\n");
while (1) {
printf("mysh> ");
if (!fgets(command, MAX_COMMAND_LENGTH, stdin)) {
// if get wrong command
break;
}
// remove line break
command[strcspn(command, "\n")] = 0;
// if input 'exit', then 'exit'
if (strcmp(command, "exit") == 0) {
printf("mysh: exiting\n");
break;
}
parse_and_execute(command);
}
}
```
## Parse and execute for command
This function parses and executes each command:
- Splits the command into arguments and checks for special characters like pipes (`|`) and wildcards (`*`).
- Handles command execution based on the presence of a pipe.
- Uses `execute_pipe_command` for piped commands or `execute_command` otherwise.
```c
void parse_and_execute(char *command) {
char *args[MAX_ARGS]; // Array to store command arguments
char *args_pipe[MAX_ARGS]; // Array to store arguments after a pipe
char *token = strtok(command, " "); // Tokenize the command string
int argc = 0, argc_pipe = 0; // Argument counters for both command parts
int pipe_found = 0; // Flag to check if a pipe is found
// Loop through tokens while the max argument count is not exceeded
while (token != NULL && argc < MAX_ARGS - 1) {
if (strcmp(token, "|") == 0) {
pipe_found = 1; // Set the pipe flag if a pipe is found
break;
}
else if (strchr(token, '*')) {
// If a wildcard is found, expand it
expand_wildcards(token, args, &argc);
}
else {
// Store the argument and increment the argument count
args[argc++] = strdup(token);
}
token = strtok(NULL, " "); // Get the next token
}
args[argc] = NULL; // Set the last element to NULL for exec
if (pipe_found) {
// If a pipe is found, process the arguments after the pipe
token = strtok(NULL, " ");
while (token != NULL && argc_pipe < MAX_ARGS - 1) {
args_pipe[argc_pipe++] = strdup(token); // Store the arguments after the pipe
token = strtok(NULL, " ");
}
args_pipe[argc_pipe] = NULL; // Set the last element to NULL for exec
execute_pipe_command(args, args_pipe); // Execute the piped command
// Free dynamically allocated memory for pipe arguments
for (int i = 0; i < argc_pipe; i++) {
free(args_pipe[i]);
}
} else {
execute_command(args); // Execute the command if no pipe is found
}
// Free dynamically allocated memory for arguments
for (int i = 0; i < argc; i++) {
free(args[i]);
}
}
```
### expand wildcards for "*"
Expands wildcard characters in commands:
- Utilizes `glob` to match file names against the pattern.
- Adds matched file names to the argument list for execution.
```c
void expand_wildcards(char *arg, char **argv, int *argc) {
glob_t glob_result;
int i;
// GLOB_NOCHECK means that `glob` will return to the original pattern if no matching file is found.
// GLOB_TILDE allows the wave symbol (`~`) to be used in the pattern to expand to the user's home directory.
if (glob(arg, GLOB_NOCHECK | GLOB_TILDE, NULL, &glob_result) == 0) {
for (i = 0; i < glob_result.gl_pathc && *argc < MAX_ARGS - 1; i++) {
argv[(*argc)++] = strdup(glob_result.gl_pathv[i]);
}
}
globfree(&glob_result);
}
```
### pip command for "|"
Handles the execution of piped commands:
- Creates a pipe and forks two processes for each part of the piped command.
- Sets up the pipe's read and write ends in the respective child processes.
- Executes both parts of the pipe using `execvp`.
```c
void execute_pipe_command(char *args1[], char *args2[]) {
int pipe_fds[2];
pid_t pid1, pid2;
if (pipe(pipe_fds) == -1) {
perror("pipe");
exit(EXIT_FAILURE);
}
pid1 = fork();
if (pid1 == 0) {
// first child
close(pipe_fds[0]);
dup2(pipe_fds[1], STDOUT_FILENO); //Redirects standard input to the read side of the pipe
close(pipe_fds[1]);
execvp(args1[0], args1);
perror("execvp");
exit(EXIT_FAILURE);
}
pid2 = fork();
if (pid2 == 0) {
// second child
close(pipe_fds[1]);
dup2(pipe_fds[0], STDIN_FILENO); //Redirects standard input to the read side of the pipe
close(pipe_fds[0]);
execvp(args2[0], args2);
perror("execvp");
exit(EXIT_FAILURE);
}
// father
close(pipe_fds[0]);
close(pipe_fds[1]);
waitpid(pid1, NULL, 0);
waitpid(pid2, NULL, 0);
}
```
### Execute command
Executes a given command:
- Checks if the command is a built-in command like `cd` or `pwd` and executes it.
- For external commands, forks a child process and uses `execvp` for execution.
- Handles command execution errors and waits for the child process to finish.
```c
void execute_command(char *argv[]) {
if (strcmp(argv[0], "cd") == 0) {
// cd command
if (argv[1] == NULL || argv[2] != NULL) {
fprintf(stderr, "cd: Error Number of Parameters\n");
} else if (chdir(argv[1]) != 0) {
perror("cd");
}
}
else if (strcmp(argv[0], "pwd") == 0) {
// pwd command
char cwd[MAX_COMMAND_LENGTH];
if (getcwd(cwd, sizeof(cwd)) != NULL) {
printf("%s\n", cwd);
}
else {
perror("pwd");
}
}
else {
// external command
pid_t pid = fork();
if (pid == -1) {
perror("fork");
}
else if (pid == 0) {
// child progress
if (execvp(argv[0], argv) == -1) {
perror("execvp");
exit(EXIT_FAILURE);
}
}
else {
// father progress
int status;
waitpid(pid, &status, 0);
}
}
}
```