操作系统实验四 (综合实验)设计简单的Shell程序 原创

直接放调整完的代码,有错误的话请在评论区指出 

有一些用于检查的代码,可以删除

仅供参考

#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程序设计实验-优快云博客

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值