---
# System prepended metadata

title: Recitation 8

---

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