# 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); } } } ```