直接放调整完的代码,有错误的话请在评论区指出
有一些用于检查的代码,可以删除
仅供参考
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/wait.h> #include <sys/types.h> #include <limits.h> #define MAX_COMMAND_LENGTH 1024 #define MAX_ARGS 128 // 函数声明 void process_command(char* cmd); void execute_internal_command(char** args); void execute_external_command(char** cmd); void execute_pipes(char* cmd); int split_cmd(char* cmd, char* splitted_cmds[]); void parse_cmd(char* cmd, char** args); int main() { char cmd[MAX_COMMAND_LENGTH]; // 存储输入的命令 char cwd[PATH_MAX]; // 存储当前工作目录的绝对路径 char *USERNAME = getenv("USER"); char HOSTNAME[HOST_NAME_MAX]; gethostname(HOSTNAME, HOST_NAME_MAX); while (1) { // 显示提示 if (getcwd(cwd, sizeof(cwd)) == NULL) { perror("getcwd"); exit(EXIT_FAILURE); } printf("%s@%s:%s$ ", USERNAME, HOSTNAME, cwd); // 获取命令 if (fgets(cmd, sizeof(cmd), stdin) == NULL) { printf("\n"); continue; } if ((strlen(cmd) > 0) && (cmd[strlen(cmd) - 1] == '\n')) { cmd[strlen(cmd) - 1] = '\0'; // 去掉末尾的换行符 } // 处理命令 process_command(cmd); } return 0; } void process_command(char* cmd) { char *cmd_copy = strdup(cmd); // 创建原始命令的副本以保护原命令 char *args[MAX_ARGS]; parse_cmd(cmd_copy, args); // 填充 args if (args[0] == NULL) { // 过滤空命令 free(cmd_copy); return; } // 检查参数 for(int j = 0; j <= 5; j++) { printf("args[%d] = %s\n", j, args[j]); } printf("Original cmd: '%s'\n", cmd); printf("Command copy: '%s'\n", cmd_copy); if (strcmp(args[0], "cd") == 0 || strcmp(args[0], "help") == 0 || strcmp(args[0], "exit") == 0) { execute_internal_command(args); } else if (strchr(cmd, '|') != NULL) { // 如果存在管道符 printf("execute pipes!\n"); execute_pipes(cmd); // 特殊处理管道命令 } else { execute_external_command(args); // 执行外部命令 } free(cmd_copy); // 释放复制的命令字符串 } void parse_cmd(char* cmd, char **args) { int i = 0; char* token = strtok(cmd, " "); while (token != NULL && i < MAX_ARGS - 1) { args[i++] = token; token = strtok(NULL, " "); } args[i] = NULL; for(int j = 0; j <= i; j++) { printf("args[%d] = %s\n", j, args[j]); } } void execute_internal_command(char** args) { // 此处实现内部命令的逻辑,如cd,help,exit if (strcmp(args[0], "cd") == 0) { if (args[1] == NULL) { fprintf(stderr, "cd: argument required\n"); } else { if (chdir(args[1]) != 0) { perror("cd"); } } } else if (strcmp(args[0], "help") == 0) { printf("Supported commands:\n"); printf("\tcd [directory]\t Change the current directory to [directory].\n"); printf("\texit\t\t Exit the shell.\n"); printf("\thelp\t\t Display this help message.\n"); printf("Type the name of an external command to execute it.\n"); printf("Use the | symbol to create a pipe between commands.\n"); } else if (strcmp(args[0], "exit") == 0) { exit(EXIT_SUCCESS); } } void execute_external_command(char** args) { // 执行外部命令,假定 args 已经正确解析 printf("Executing command: %s\n", args[0]); // 执行命令前的参数检查 for(int j = 0; args[j] != NULL; j++) { printf("args[%d] = %s\n", j, args[j]); } pid_t pid = fork(); if (pid == 0) { // 子进程 if (execvp(args[0], args) == -1) { perror("execvp"); exit(EXIT_FAILURE); } } else if (pid < 0) { // 错误 perror("fork"); } else { // 父进程 int status; waitpid(pid, &status, 0); } } void execute_pipes(char* cmd) { char* splitted_cmds[MAX_ARGS]; // 存储分割后的多个命令 int cmds_count = split_cmd(cmd, splitted_cmds); // 拆分命令并得到命令数量 int in_fd = 0; // 初始时,用于第一个命令的输入文件描述符指向标准输入 int fd[2]; // 用于创建管道的文件描述符数组 for (int i = 0; i < cmds_count; i++) { if (i < cmds_count - 1) { // 如果不是最后一个命令,则需要创建管道 if (pipe(fd) < 0) { perror("pipe"); exit(EXIT_FAILURE); } } pid_t pid = fork(); if (pid == 0) { // 子进程 if (i < cmds_count - 1) { // 如果不是最后一个命令 if (dup2(fd[1], STDOUT_FILENO) < 0) { // 将标准输出重定向到管道的写端 perror("dup2"); exit(EXIT_FAILURE); } } if (in_fd != STDIN_FILENO) { // 如果前一个命令的输出被重定向了 if (dup2(in_fd, STDIN_FILENO) < 0) { // 将标准输入重定向到管道的读端 perror("dup2"); exit(EXIT_FAILURE); } } for (int j = 0; j < 2; j++) { // 关闭管道文件描述符 close(fd[j]); } // 执行当前命令 char* args[MAX_ARGS]; parse_cmd(splitted_cmds[i], args); // 解析命令参数 execvp(args[0], args); // 如果execvp返回,说明发生了错误 perror("execvp"); exit(EXIT_FAILURE); } else if (pid < 0) { // 如果fork失败 perror("fork"); exit(EXIT_FAILURE); } // 父进程 if (in_fd != STDIN_FILENO) { close(in_fd); // 关闭上个管道的读端 } if (i < cmds_count - 1) { close(fd[1]); // 关闭当前管道的写端 in_fd = fd[0]; // 为下个命令设置管道的读端 } } // 父进程在最后关闭最终的文件描述符,并且等待所有子进程结束 if (in_fd != STDIN_FILENO) { close(in_fd); } for (int i = 0; i < cmds_count; i++) { wait(NULL); // 等待所有子进程结束 } } int split_cmd(char* cmd, char* splitted_cmds[]) { int i = 0; char* token = strtok(cmd, "|"); while (token != NULL) { splitted_cmds[i++] = token; token = strtok(NULL, "|"); } splitted_cmds[i] = NULL; // 设置数组的最后一个元素为NULL,标识命令的结束 printf("split_cmd number is %d\n",i); return i; // 返回拆分后的命令数 }
更加详细的教程,请看参考文章:操作系统实验四 (综合实验)设计简单的Shell程序_简单shell程序设计实验-优快云博客