Files
C-exp-collection/exp0.5/task52.c
2026-05-15 21:25:01 +08:00

259 lines
7.3 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#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;
}
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;
}
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;
}
static void exec_cmd_in_child(char **args, int argc, int in_fd, int out_fd) {
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);
}
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);
}
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);
}
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);
}
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);
}
}
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); /* 多段管道命令 */
}
}
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;
}