一、项目背景与目标
本实验旨在通过设计一个C程序来实现一个具有历史功能的UNIX Shell接口。该程序将接受用户输入的命令,并在单独的进程中执行每个命令。用户输入的命令将被解析为令牌,并存储在字符字符串数组中,随后由execvp()函数执行。此外,该程序还将实现一个历史功能,允许用户访问最近输入的命令。
二、项目设计与实现
1.创建子进程并执行命令
首先,修改main()函数,使其能够分叉一个子进程并执行用户指定的命令。使用fork()系统调用来创建子进程,并在子进程中调用execvp()函数来执行命令。为了解析用户输入的命令,我将其拆分为令牌,并存储在args数组中。该数组随后被传递给execvp()函数。
我们还需要检查用户是否在命令末尾添加了&符号,以确定父进程是否应该等待子进程退出。如果用户添加了&符号,则父进程将继续执行,而不会等待子进程完成。
2.实现历史功能
接下来,修改Shell接口程序,使其提供历史功能。该程序维护了一个包含最近10条命令的历史缓冲区。用户可以通过输入“History”命令来列出最近输入的命令。命令将按从最近到最旧的顺序编号。
还需要实现两种从命令历史中检索命令的技术:
当用户输入!!时,将执行历史中最近的命令。
当用户输入一个单感叹号后跟一个整数N时,将执行历史中的第N条命令。
任何以这种方式执行的命令都将在用户的屏幕上回显,并放置在历史缓冲区中作为下一条命令。
添加基本的错误处理功能。如果历史中没有命令,输入!!将显示消息“No commands in history.”。如果输入的单感叹号后跟的整数N没有对应的命令,则程序将输出“No such command in history.”。
三、实验结果与分析
通过多次运行程序并输入各种命令来测试Shell的功能。以下是部分实验结果:
输入命令“cat prog.c”并回车,程序正确显示了文件prog.c的内容。
输入命令“ls -l &”并回车,程序在后台列出了当前目录的内容,并立即返回了提示符。
输入命令“History”并回车,程序列出了最近输入的10条命令。
输入“!!”并执行了最近的一条命令。
输入“!3”并执行了历史中的第三条命令。
我们还注意到,在程序运行期间,如果输入的命令无效或无法执行,程序会正确地显示错误信息,并继续等待用户的下一个输入。
四、遇到的问题与解决方案
在项目实施过程中,我遇到了以下问题:
在解析用户输入的命令时,我发现需要正确地处理空格和特殊字符。为此,我使用了strtok()函数来拆分输入字符串,并使用isspace()函数来检查字符是否为空格。
在实现历史功能时,需要确保历史缓冲区的大小足够容纳最近10条命令。我使用了循环数组来实现这一点,当达到数组末尾时,会从数组的开头开始覆盖旧命令。
在处理后台进程时,需要确保父进程不会等待所有子进程都完成后再继续执行。为此,我在创建子进程后立即检查了命令中是否包含&符号,并相应地调整了父进程的等待行为。
五、建议与评论
通过本项目,我加深了对操作系统shell和系统调用的理解,并学会了如何创建进程并协调父进程和子进程的运行。此外,我还学会了如何实现简单的历史功能来增强用户体验。我认为这是一个非常有价值且有趣的项目,它让我们在实践中深入了解了UNIX Shell的工作原理。
六、源代码附录
// Project 01 UNIX Shell with History Feature.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。 // #include #include #include #include #include <sys/types.h> #include <sys/wait.h> #define MAX_LINE 80 #define MAX_HISTORY 10 char* history[MAX_HISTORY]; int history_count = 0; void add_to_history(char* command) { if (history_count < MAX_HISTORY) { history[history_count++] = strdup(command); } else { for (int i = 0; i < MAX_HISTORY - 1; i++) { history[i] = history[i + 1]; } history[MAX_HISTORY - 1] = strdup(command); } } void list_history() { printf("Command history:\n"); for (int i = history_count - 1; i >= 0 && i >= history_count - MAX_HISTORY; i--) { printf("%d %s\n", history_count - i, history[i]); } } void execute_history_command(char* input) { if (strcmp(input, "!!") == 0) { if (history_count > 0) { char* command = history[history_count - 1]; printf("Executing: %s\n", command); add_to_history(command); // Place the command back in history as the next command char* args[MAX_LINE / 2 + 1]; parse_command(command, args); execute_command(args); } else { printf("No commands in history.\n"); } } else if (input[0] == '!' && isdigit(input[1])) { int index = atoi(input + 1); if (index > 0 && index <= history_count) { char* command = history[history_count - index]; printf("Executing: %s\n", command); add_to_history(command); // Place the command back in history as the next command char* args[MAX_LINE / 2 + 1]; parse_command(command, args); execute_command(args); } else { printf("No such command in history.\n"); } } } void parse_command(char* line, char** args) { char* token = strtok(line, " \t\r\n\a"); int i = 0; while (token != NULL && i < MAX_LINE / 2) { args[i++] = token; token = strtok(NULL, " \t\r\n\a"); } args[i] = NULL; } void execute_command(char** args) { pid_t pid = fork(); if (pid == 0) { // Child process if (execvp(args[0], args) == -1) { perror("execvp"); exit(EXIT_FAILURE); } } else if (pid > 0) { // Parent process if (strchr(args[0], '&') == NULL) { wait(NULL); // Wait for the child process to exit } } else { // Fork failed perror("fork"); exit(EXIT_FAILURE); } } int main() { char line[MAX_LINE]; int should_run = 1; while (should_run) { printf("osh> "); fflush(stdout); if (fgets(line, MAX_LINE, stdin) == NULL) { perror("fgets"); exit(EXIT_FAILURE); } // Remove newline character at the end of the input line[strcspn(line, "\n")] = 0; // Check for exit command if (strcmp(line, "exit") == 0) { should_run = 0; } else if (line[0] == '!' && (line[1] == '!' || isdigit(line[1]))) { // Handle history commands execute_history_command(line); } else { // Normal command char* args[MAX_LINE / 2 + 1]; parse_command(line, args); // Check for background execution int bg = 0; // By default, not running in background if (args[argc(args) - 1] && strcmp(args[argc(args) - 1], "&") == 0) { bg = 1; args[argc(args) - 1] = NULL; // Remove '&' from arguments } execute_command(args); // If not running in background, wait for command to complete if (!bg) { wait(NULL); // Reap any zombie processes } // Add the command to history add_to_history(line); } } // Free allocated memory for history commands for (int i = 0; i < history_count; i++) { free(history[i]); } return 0; } // Helper function to get the number of arguments in an argv-like array int argc(char** args) { int count = 0; while (args[count] != NULL) { count++; } return count; }