自制Linux Shell重定向功能内存异常问题求助
问题描述
我正在开发一款基于Linux的Shell程序,通过fork创建子进程执行命令。目前尝试实现command < filename1 > filename2形式的重定向功能,即命令从filename1读取输入,将输出写入filename2。
测试命令为:
sort < t > out
其中t为filename1,out为filename2。我编写make_tokens3和make_tokens4函数拆分命令字符串,先将其拆分为"sort"和"t > out",再拆分"t > out"为"t"和"out"。
但运行时出现问题:调用make_tokens4时,之前拆分得到的"sort"变量值变为随机字符串;长文件名(如out.txt)会被错误读取为out.txl,所有重定向符号均存在该问题。
可运行代码
#define MAXARGS 128 #define MAXLINE 256 #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> /* function prototypes */ void eval(char* cmdline); int parseline(char* buf, char** argv); void make_tokens2(char* cmd, char* arg[], char* arg2[], char* target); void make_tokens3(char* cmd, char* arg[], char* arg2, char* target); void make_tokens4(char* cmd, char arg[], char arg2[], char* target); int main() { char cmdline[MAXLINE]; /* Command line */ char* ret; while (1) { /* Read */ printf("mini> "); ret = fgets(cmdline, MAXLINE, stdin); if (feof(stdin) || ret == NULL) exit(0); /* Evaluate */ eval(cmdline); } } void eval(char* cmdline) { char* argv[MAXARGS] = { NULL, }; /* Argument list execve() */ char buf[MAXLINE] = ""; /* Holds modified command line */ int bg; /* Should the job run in bg or fg? */ pid_t pid; /* Process id */ strcpy(buf, cmdline); bg = parseline(buf, argv); /* Ignore empty lines */ if (argv[0] == NULL) return; /* Child runs user job */ if ((pid = fork()) == 0) { setpgid(pid, 0); printf("parent1: %ld in %ld\n", (long)getpid(),(long)getpgid(pid)); char* argf[MAXARGS] = { NULL, }; if (strchr(buf, '<') != NULL) { //This is where the problem is if (strchr(buf, '>') != NULL) { //read from and write to file char* arg[MAXARGS] = {NULL}; char arg2[MAXLINE]; char* arg3 = malloc(MAXARGS); char* arg4 = malloc(MAXARGS); make_tokens3(buf, arg, arg2, "<"); printf("arg[0]: (%s)\n", arg[0]); printf("arg2: (%s)\n", arg2); make_tokens4(arg2, arg3, arg4, ">"); printf("arg[0]: (%s)\n", arg[0]); if (arg[1] != NULL) printf("arg[1]: %s\n", arg[1]); int fd1 = open(arg3, O_RDONLY | O_CREAT, 00777); if (fd1 == -1) { perror("mini"); exit(1); } int fd2 = open(arg4, O_WRONLY | O_CREAT, 00777); if (fd2 == -1) { perror("mini"); exit(1); } if (fd1 != -1 && fd2 != -1) { printf("arg3: (%s), arg4: (%s)\n", arg3, arg4); dup2(fd1, 0); dup2(fd2, 1); if (execvp(arg[0], arg) < 0) { perror("execvp"); } } close(fd1); close(fd2); free(arg3); free(arg4); } else { //read from file char* arg[MAXARGS]; char* arg2[MAXARGS]; make_tokens2(buf, arg, arg2, "<"); int fd1 = open(arg2[0], O_RDONLY | O_CREAT, 00777); if (fd1 == -1) { perror("mini"); exit(1); } else if (fd1 != -1) { dup2(fd1, fileno(stdin)); execvp(arg[0], arg); } close(fd1); } } else if (strstr(buf, ">>") != NULL) {//this means to append to file char* arg[MAXARGS] = { NULL, }; char* arg2[MAXARGS] = { NULL, }; make_tokens2(buf, arg, arg2, ">>"); int fd1 = open(arg2[0], O_WRONLY | O_APPEND | O_CREAT, 00777); if (fd1 == -1) { perror("mini"); exit(1); } else if (fd1 != -1) { dup2(fd1, fileno(stdout)); execvp(arg[0], arg); } close(fd1); } else if (strchr(buf, '>') != NULL) {//this means to overwrite file char* arg[MAXARGS] = { NULL, }; char* arg2[MAXARGS] = { NULL, }; make_tokens2(buf, arg, arg2, ">"); int fd1 = open(arg2[0], O_WRONLY | O_CREAT, 00777); if (fd1 == -1) { perror("mini"); exit(1); } else if (fd1 != -1) { dup2(fd1, fileno(stdout)); execvp(arg[0], arg); } close(fd1); } else { //normal command with no redirection if (execvp(argv[0], argv) < 0) { perror("execvp"); fprintf(stderr, "%s: Command not found.\n", argv[0]); exit(1); } } exit(0);//parent process exit } /* Parent waits for foreground job to terminate */ if (!bg) { int status; pid_t waitstate = waitpid(pid, &status, 0); if (waitstate < 0) printf("waitfg: waitpid error"); else if (waitstate >= 0) printf("wait for %d\n", pid); if (WIFEXITED(status)) { int st = WEXITSTATUS(status); printf("Exit status for child %ld: %d\n", (long)pid, st); } } else printf("%d %s", pid, cmdline); } int builtin_command(char** argv) { return 0; } int parseline(char* buf, char** argv) { int argc; /* Number of args */ int bg; /* Background job? */ char buf2[MAXLINE] = ""; while (*buf && (*buf == ' ')) buf++; buf[strlen(buf) - 1] = ' ';/* Replace trailing '\n' with space */ strcpy(buf2, buf); /* Build the argv list */ argc = 0; char* ptr = strtok(buf2, " "); while (ptr != NULL) { argv[argc++] = ptr; ptr = strtok(NULL, " "); } argv[argc] = NULL; /* Ignore blank line */ if (argc == 0) return 1; /* Should the job run in the background? */ if ((bg = (*argv[argc - 1] == '&')) != 0) argv[--argc] = NULL; return bg; } /* $end parseline */ void make_tokens2(char* cmd, char* arg[], char* arg2[], char* target) { char* left = NULL, * right = NULL, * ptr = NULL; int num = 0; char cmd2[MAXLINE] = ""; char* saveptr = NULL; strcpy(cmd2, cmd); left = strtok_r(cmd2, target, &saveptr); if (left) left[strcspn(left, " \t")] = '\0'; right = strtok_r(NULL, target, &saveptr); if (right) right[strcspn(right, " \t")] = '\0'; char* arg_saveptr = NULL; if (left) { ptr = strtok_r(left, " ", &arg_saveptr); while (ptr != NULL) { arg[num++] = ptr; ptr = strtok_r(NULL, " ", &arg_saveptr); } } arg[num] = NULL; num = 0; char* arg2_saveptr = NULL; if (right) { ptr = strtok_r(right, " ", &arg2_saveptr); while (ptr != NULL) { arg2[num++] = ptr; ptr = strtok_r(NULL, " ", &arg2_saveptr); } } arg2[num] = NULL; return; } void make_tokens3(char* cmd, char* arg[], char* arg2, char* target) { char* left = NULL, * right = NULL, * ptr = NULL; int num = 0; char cmd2[MAXLINE] = ""; char* saveptr = NULL; strcpy(cmd2, cmd); left = strtok_r(cmd2, target, &saveptr); if (left) left[strcspn(left, " \t")] = '\0'; right = strtok_r(NULL, target, &saveptr); if (right) right[strcspn(right, " \t")] = '\0'; char* arg_saveptr = NULL; if (left) { ptr = strtok_r(left, " ", &arg_saveptr); while (ptr != NULL) { arg[num++] = ptr; ptr = strtok_r(NULL, " ", &arg_saveptr); } } arg[num] = NULL; if (right) { strcpy(arg2, right); } else { arg2[0] = '\0'; } return; } void make_tokens4(char* cmd, char argc[], char argc2[], char* target) { char* left = NULL, * right = NULL; char* saveptr = NULL; while (*cmd && isspace((unsigned char)*cmd)) cmd++; left = strtok_r(cmd, target, &saveptr); right = strtok_r(NULL, target, &saveptr); if (left) { char* fname = strtok_r(left, " ", &saveptr); if (fname) { strncpy(argc, fname, MAXLINE - 1); argc[MAXLINE - 1] = '\0'; } else { argc[0] = '\0'; } } else { argc[0] = '\0'; } if (right) { char* fname = strtok_r(right, " ", &saveptr); if (fname) { strncpy(argc2, fname, MAXLINE - 1); argc2[MAXLINE - 1] = '\0'; } else { argc2[0] = '\0'; } } else { argc2[0] = '\0'; } return; }
问题根源与修复
核心问题
strtok全局状态冲突:strtok使用全局静态变量保存拆分位置,make_tokens3调用后,make_tokens4再次调用会破坏之前的状态,导致arg数组指向的内存失效,出现随机字符串。strncpy错误使用:sizeof(ptr)是指针的大小(通常8字节),不是字符串长度,导致长文件名被截断;且strncpy不自动添加终止符,引发乱码。- 未初始化指针:
char* arg[MAXARGS];未初始化,直接赋值会导致未定义行为。
修复措施
- 替换
strtok为strtok_r:使用可重入版本,通过用户提供的指针保存拆分状态,避免全局冲突。 - 修复字符串复制逻辑:用
strlen获取实际长度,手动添加终止符;或用strncpy时指定足够长度并强制添加终止符。 - 初始化指针数组:声明时直接初始化为
{NULL},避免野指针。 - 优化文件打开逻辑:添加
O_CREAT标志,避免文件不存在时打开失败;移除无效的close调用(fd == -1时无需关闭)。
内容的提问来源于stack exchange,提问作者newb hi
相关产品推荐
相关产品推荐

