You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

自制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;
}
问题根源与修复

核心问题

  1. strtok全局状态冲突:strtok使用全局静态变量保存拆分位置,make_tokens3调用后,make_tokens4再次调用会破坏之前的状态,导致arg数组指向的内存失效,出现随机字符串。
  2. strncpy错误使用:sizeof(ptr)是指针的大小(通常8字节),不是字符串长度,导致长文件名被截断;且strncpy不自动添加终止符,引发乱码。
  3. 未初始化指针:char* arg[MAXARGS];未初始化,直接赋值会导致未定义行为。

修复措施

  1. 替换strtok为strtok_r:使用可重入版本,通过用户提供的指针保存拆分状态,避免全局冲突。
  2. 修复字符串复制逻辑:用strlen获取实际长度,手动添加终止符;或用strncpy时指定足够长度并强制添加终止符。
  3. 初始化指针数组:声明时直接初始化为{NULL},避免野指针。
  4. 优化文件打开逻辑:添加O_CREAT标志,避免文件不存在时打开失败;移除无效的close调用(fd == -1时无需关闭)。

内容的提问来源于stack exchange,提问作者newb hi

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.12 11:21:01