259 lines
7.3 KiB
C
259 lines
7.3 KiB
C
#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;
|
||
} |