添加以前的作业

This commit is contained in:
2026-05-15 21:25:01 +08:00
parent fa2f3e2413
commit 56a4233098
10 changed files with 1558 additions and 0 deletions

294
exp0.5/task52_readable.c Normal file
View File

@@ -0,0 +1,294 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <ctype.h>
#define MAX_CMD 1024
#define MAX_ARGS 64
#define MAX_PIPES 32
/* =========================================================
* 工具函数
* ========================================================= */
/* 去除字符串首尾空白字符(在原字符串上修改) */
char *trim(char *s) {
while (isspace((unsigned char)*s)) s++;
if (*s == '\0') return s;
char *end = s + strlen(s) - 1;
while (end > s && isspace((unsigned char)*end)) end--;
*(end + 1) = '\0';
return s;
}
/* 把命令字符串按空格拆成 args[],返回参数个数 */
int parse_args(char *cmd, char **args) {
int argc = 0;
char *token = strtok(cmd, " \t");
while (token != NULL && argc < MAX_ARGS - 1) {
args[argc++] = token;
token = strtok(NULL, " \t");
}
args[argc] = NULL;
return argc;
}
/* =========================================================
* 第一步辅助函数split_pipeline
* 把 "cmd1 | cmd2 | cmd3" 按 '|' 拆成若干段,
* 存入 segments[],返回段数。
* 注意:会修改 buf 的内容(把 '|' 替换为 '\0')。
* ========================================================= */
int split_pipeline(char *buf, char *segments[], int max_segs) {
int nseg = 0;
char *seg_start = buf; /* 当前段的起始位置 */
char *p = buf;
while (*p != '\0') {
if (*p == '|') {
/* 找到管道符:截断当前段,保存去空格后的指针 */
*p = '\0';
char *seg = trim(seg_start);
if (seg[0] != '\0') { /* 跳过空段 */
if (nseg >= max_segs) {
fprintf(stderr, "shell: too many pipes (max %d)\n", max_segs);
return -1;
}
segments[nseg++] = seg;
}
seg_start = p + 1; /* 下一段从 '|' 后面开始 */
}
p++;
}
/* 保存最后一段('|' 后面,或整个命令没有 '|' 时) */
char *last = trim(seg_start);
if (last[0] != '\0') {
segments[nseg++] = last;
}
return nseg;
}
/* =========================================================
* 第二步辅助函数exec_cmd_in_child
* 在子进程里执行一条命令,处理 < 和 > 重定向。
* in_fd / out_fd 来自管道(或 STDIN/STDOUT_FILENO
* 此函数只能在 fork() 后的子进程里调用,内部会 exit()。
* ========================================================= */
static void exec_cmd_in_child(char **args, int argc, int in_fd, int out_fd) {
/* --- 解析重定向符,把 < file 和 > file 从参数里剥离 --- */
char *infile = NULL;
char *outfile = NULL;
char *clean_args[MAX_ARGS];
int clean_argc = 0;
for (int i = 0; i < argc; i++) {
if (strcmp(args[i], "<") == 0 && i + 1 < argc) {
infile = args[++i]; /* 记录输入文件,跳过文件名 */
} else if (strcmp(args[i], ">") == 0 && i + 1 < argc) {
outfile = args[++i]; /* 记录输出文件,跳过文件名 */
} else {
clean_args[clean_argc++] = args[i]; /* 普通参数保留 */
}
}
clean_args[clean_argc] = NULL;
if (clean_argc == 0) exit(0);
/* --- 设置标准输入 ---
* 优先级:< 文件重定向 > 管道读端 > 默认终端 stdin */
if (infile) {
if (in_fd != STDIN_FILENO) close(in_fd); /* 管道读端不再需要,关掉 */
int fd = open(infile, O_RDONLY);
if (fd < 0) { perror("open"); exit(1); }
dup2(fd, STDIN_FILENO);
close(fd);
} else if (in_fd != STDIN_FILENO) {
dup2(in_fd, STDIN_FILENO);
close(in_fd);
}
/* --- 设置标准输出 ---
* 优先级:> 文件重定向 > 管道写端 > 默认终端 stdout */
if (outfile) {
if (out_fd != STDOUT_FILENO) close(out_fd);
int fd = open(outfile, O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (fd < 0) { perror("open"); exit(1); }
dup2(fd, STDOUT_FILENO);
close(fd);
} else if (out_fd != STDOUT_FILENO) {
dup2(out_fd, STDOUT_FILENO);
close(out_fd);
}
execvp(clean_args[0], clean_args);
fprintf(stderr, "shell: command not found: %s\n", clean_args[0]);
exit(127);
}
/* =========================================================
* 第三步辅助函数run_single
* 执行单条命令无管道fork 一个子进程并等待它结束。
* ========================================================= */
static void run_single(char *seg) {
char buf[MAX_CMD];
strncpy(buf, seg, MAX_CMD - 1);
buf[MAX_CMD - 1] = '\0';
char *args[MAX_ARGS];
int argc = parse_args(buf, args);
if (argc == 0) return;
pid_t pid = fork();
if (pid < 0) { perror("fork"); return; }
if (pid == 0) {
/* 子进程:从终端 stdin 读,输出到终端 stdout */
exec_cmd_in_child(args, argc, STDIN_FILENO, STDOUT_FILENO);
}
/* 父进程:等待子进程结束 */
waitpid(pid, NULL, 0);
}
/* =========================================================
* 第四步辅助函数run_pipeline
* 执行多段管道命令。
*
* 核心思路(以 "A | B | C" 为例):
*
* 终端stdin ──> [A] ──pipe0──> [B] ──pipe1──> [C] ──> 终端stdout
*
* 1. 创建 nseg-1 个管道pipe0, pipe1, ...
* 2. 依次 fork 每段命令的子进程,子进程拿到自己的 in_fd/out_fd
* 3. 父进程关闭所有管道端,然后等待全部子进程结束
* ========================================================= */
static void run_pipeline(char *segments[], int nseg) {
int npipes = nseg - 1;
/* --- 1. 一次性创建所有管道 --- */
int pipes[MAX_PIPES][2];
for (int i = 0; i < npipes; i++) {
if (pipe(pipes[i]) < 0) {
perror("pipe");
/* 出错:关闭已创建的管道后返回 */
for (int j = 0; j < i; j++) {
close(pipes[j][0]);
close(pipes[j][1]);
}
return;
}
}
/* --- 2. 依次 fork 每段命令的子进程 --- */
pid_t pids[MAX_PIPES + 1];
for (int i = 0; i < nseg; i++) {
/* 确定本段从哪里读、往哪里写:
* 第一段:从终端 stdin 读
* 中间段:从上一个管道的读端读,向下一个管道的写端写
* 最后段:向终端 stdout 写 */
int in_fd = (i == 0) ? STDIN_FILENO : pipes[i - 1][0];
int out_fd = (i == nseg - 1) ? STDOUT_FILENO : pipes[i][1];
/* 解析本段命令的参数 */
char cbuf[MAX_CMD];
strncpy(cbuf, segments[i], MAX_CMD - 1);
cbuf[MAX_CMD - 1] = '\0';
char *args[MAX_ARGS];
int argc = parse_args(cbuf, args);
pid_t pid = fork();
if (pid < 0) {
perror("fork");
/* fork 失败:关闭所有管道,等待已启动的子进程 */
for (int j = 0; j < npipes; j++) {
close(pipes[j][0]);
close(pipes[j][1]);
}
for (int j = 0; j < i; j++) waitpid(pids[j], NULL, 0);
return;
}
if (pid == 0) {
/* 子进程:
* 关闭所有"不是自己用"的管道端,
* 防止 EOF 永远不到达读端。 */
for (int j = 0; j < npipes; j++) {
if (pipes[j][0] != in_fd) close(pipes[j][0]);
if (pipes[j][1] != out_fd) close(pipes[j][1]);
}
if (argc == 0) exit(0);
exec_cmd_in_child(args, argc, in_fd, out_fd);
/* exec_cmd_in_child 内部会 exit永远不会走到这里 */
}
pids[i] = pid; /* 父进程记录子进程 PID */
}
/* --- 3. 父进程关闭所有管道端,再统一等待子进程 ---
* 必须先全部关闭,否则最后一段命令永远读不到 EOF。 */
for (int i = 0; i < npipes; i++) {
close(pipes[i][0]);
close(pipes[i][1]);
}
for (int i = 0; i < nseg; i++) {
waitpid(pids[i], NULL, 0);
}
}
/* =========================================================
* 顶层入口execute
* 只做"分流"
* - 把命令行按 '|' 分段
* - 1 段 → run_single
* - 多段 → run_pipeline
* ========================================================= */
void execute(char *cmdline) {
char buf[MAX_CMD];
strncpy(buf, cmdline, MAX_CMD - 1);
buf[MAX_CMD - 1] = '\0';
char *segments[MAX_PIPES + 1];
int nseg = split_pipeline(buf, segments, MAX_PIPES);
if (nseg <= 0) return; /* 空命令或出错 */
if (nseg == 1) {
run_single(segments[0]); /* 普通命令,无管道 */
} else {
run_pipeline(segments, nseg); /* 多段管道命令 */
}
}
/* =========================================================
* main读取命令行循环执行
* ========================================================= */
int main() {
char cmdline[MAX_CMD];
while (1) {
printf("%% ");
fflush(stdout);
if (fgets(cmdline, MAX_CMD, stdin) == NULL) {
printf("\n");
break;
}
char *cmd = trim(cmdline);
if (strlen(cmd) == 0) continue;
if (strcmp(cmd, "exit") == 0 || strcmp(cmd, "logout") == 0) {
printf("Goodbye!\n");
break;
}
execute(cmd);
}
return 0;
}