Unix Shell 在 C 中实现 Cat - 文件描述符问题

发布于 2024-10-19 04:22:07 字数 4270 浏览 4 评论 0原文

我已经完成了 Unix shell 的实践实现,但当其输出到文件时,我在实现 cat遇到了问题; IE: cat foo.txt > bar.txt - 将foo的内容输出到bar

让我们从ma​​in函数开始然后我将定义子方法:

int main(int argc, char **argv)
{           
    printf("[MYSHELL] $ ");

    while (TRUE) {
        user_input = getchar();
        switch (user_input) {

            case EOF:
                exit(-1);

            case '\n':
                printf("[MYSHELL] $ ");
                break;

            default:
                // parse input into cmd_argv - store # commands in cmd_argc
                handle_user_input();

                //determine input and execute foreground/background process
                execute_command();
        }
        background = 0;
    }
    printf("\n[MYSHELL] $ ");
    return 0;    
}

handle_user_input 只是填充 cmd_argv 数组来执行 user_input,并删除> 并设置 output 标志(如果用户希望输出到文件)。这是该方法的核心内容:

while (buffer_pointer != NULL) { 
        cmd_argv[cmd_argc] = buffer_pointer;
        buffer_pointer = strtok(NULL, " ");

        if(strcmp(cmd_argv[cmd_argc], ">") == 0){
            printf("\nThere was a '>' in %s @ index: %d for buffer_pointer: %s \n", *cmd_argv,cmd_argc,buffer_pointer);
            cmd_argv[cmd_argc] = strtok(NULL, " ");
            output = 1;
        }

        cmd_argc++;

        if(output){
            filename = buffer_pointer;
            printf("The return of handling input for filename %s =  %s + %s \n", buffer_pointer, cmd_argv[0], cmd_argv[1]); 
            return;
        }        
}

然后调用 execute_command,解释现在填充的 cmd_argv。只是为了让您了解全局。显然,这些情况都不匹配,并且调用了 create_process 方法:

int execute_command()
{
    if (strcmp("pwd", cmd_argv[0]) == 0){
        printf("%s\n",getenv("PATH"));  
        return 1;
    }
    else if(strcmp("cd", cmd_argv[0]) == 0){
        change_directory();
        return 1;        
    }
    else if (strcmp("jobs", cmd_argv[0]) == 0){
        display_job_list();
        return 1;   
    }
    else if (strcmp("kill", cmd_argv[0]) == 0){
        kill_job();
    }
    else if (strcmp("EOT", cmd_argv[0]) == 0){
        exit(1);
    }
    else if (strcmp("exit", cmd_argv[0]) == 0){
        exit(-1);
    }
    else{
        create_process();
        return;
    }
}

非常简单,对吧?


create_process是我遇到问题的地方。

void create_process()
{
    status = 0;
    int pid = fork();
    background = 0;

    if (pid == 0) {
        // child process
        if(output){
            printf("Output set in create process to %d\n",output);
            output = 0;
            int output_fd = open(filename, O_RDONLY);
            printf("Output desc = %d\n",output_fd);
            if (output_fd > -1) {
                dup2(output_fd, STDOUT_FILENO);
                close(output_fd);
            } else {
                perror("open");
            }
        }
        printf("Executing command, but STDOUT writing to COMMAND PROMPT instead of FILE - as I get the 'open' error above \n");
        execvp(*cmd_argv,cmd_argv);
        // If an error occurs, print error and exit
        fprintf (stderr, "unknown command: %s\n", cmd_argv[0]);
        exit(0);
    } else {
        // parent process, waiting on child process
            waitpid(pid, &status, 0);
        if (status != 0)
            fprintf  (stderr, "error: %s exited with status code %d\n", cmd_argv[0], status);
    }
    return;
}

我打印了 output_fd = -1,并且我设法在 else 中得到 perror("open") ,说明: open: No such file or directory.然后它打印出它是“写入命令提示符而不是文件”,正如我在控制台上显示的那样。然后执行execvp,它处理cat foo.txt,但将其打印到控制台而不是文件。

我意识到此时不应该,因为 output_fd = -1 是不可取的,应该返回另一个值;但我无法弄清楚如何正确使用文件描述符以便使用 cat foo.txt > 打开新的/现有的文件bar.txt 并写入它,以及返回到命令行的标准输入。

我已设法输出到文件,但随后失去了正确的标准输入。有人可以指导我到这里吗?我觉得我正在为自己做错或正在寻找的一些愚蠢的事情而兜圈子。

非常感谢任何帮助。

I've about got my practice implementation of a Unix shell done, except I'm having an issue with implementing cat when its output is to a file; IE: cat foo.txt > bar.txt - outputting foo's contents to bar.

Let's start from the main function & then I'll define the submethods:

int main(int argc, char **argv)
{           
    printf("[MYSHELL] $ ");

    while (TRUE) {
        user_input = getchar();
        switch (user_input) {

            case EOF:
                exit(-1);

            case '\n':
                printf("[MYSHELL] $ ");
                break;

            default:
                // parse input into cmd_argv - store # commands in cmd_argc
                handle_user_input();

                //determine input and execute foreground/background process
                execute_command();
        }
        background = 0;
    }
    printf("\n[MYSHELL] $ ");
    return 0;    
}

handle_user_input just populates the cmd_argv array to execute the user_input, and removes the > and sets an output flag if the user wishes to output to a file. This is the meat of that method:

while (buffer_pointer != NULL) { 
        cmd_argv[cmd_argc] = buffer_pointer;
        buffer_pointer = strtok(NULL, " ");

        if(strcmp(cmd_argv[cmd_argc], ">") == 0){
            printf("\nThere was a '>' in %s @ index: %d for buffer_pointer: %s \n", *cmd_argv,cmd_argc,buffer_pointer);
            cmd_argv[cmd_argc] = strtok(NULL, " ");
            output = 1;
        }

        cmd_argc++;

        if(output){
            filename = buffer_pointer;
            printf("The return of handling input for filename %s =  %s + %s \n", buffer_pointer, cmd_argv[0], cmd_argv[1]); 
            return;
        }        
}

execute_command is then called, interpreting the now populated cmd_argv. Just to give you an idea of the big picture. Obviously, none of these cases match and the create_process method is called:

int execute_command()
{
    if (strcmp("pwd", cmd_argv[0]) == 0){
        printf("%s\n",getenv("PATH"));  
        return 1;
    }
    else if(strcmp("cd", cmd_argv[0]) == 0){
        change_directory();
        return 1;        
    }
    else if (strcmp("jobs", cmd_argv[0]) == 0){
        display_job_list();
        return 1;   
    }
    else if (strcmp("kill", cmd_argv[0]) == 0){
        kill_job();
    }
    else if (strcmp("EOT", cmd_argv[0]) == 0){
        exit(1);
    }
    else if (strcmp("exit", cmd_argv[0]) == 0){
        exit(-1);
    }
    else{
        create_process();
        return;
    }
}

Pretty straight forward, right?


create_process is where I'm having issues.

void create_process()
{
    status = 0;
    int pid = fork();
    background = 0;

    if (pid == 0) {
        // child process
        if(output){
            printf("Output set in create process to %d\n",output);
            output = 0;
            int output_fd = open(filename, O_RDONLY);
            printf("Output desc = %d\n",output_fd);
            if (output_fd > -1) {
                dup2(output_fd, STDOUT_FILENO);
                close(output_fd);
            } else {
                perror("open");
            }
        }
        printf("Executing command, but STDOUT writing to COMMAND PROMPT instead of FILE - as I get the 'open' error above \n");
        execvp(*cmd_argv,cmd_argv);
        // If an error occurs, print error and exit
        fprintf (stderr, "unknown command: %s\n", cmd_argv[0]);
        exit(0);
    } else {
        // parent process, waiting on child process
            waitpid(pid, &status, 0);
        if (status != 0)
            fprintf  (stderr, "error: %s exited with status code %d\n", cmd_argv[0], status);
    }
    return;
}

My printed output_fd = -1, and I manage to get the perror("open") inside the else stating: open: No such file or directory. It then prints that it's "writing to COMMAND PROMPT instead of FILE", as I display to the console. Then executes execvp which handles cat foo.txt, but prints it to the console instead of the file.

I realize it shouldn't at this point, as having output_fd = -1 isnt desirable and should be returning another value; but I cant figure out how to use file descriptors correctly in order to open a new/existing file with cat foo.txt > bar.txt and write to it, as WELL AS GET BACK to the command line's stdin.

I have managed to output to the file, but then lose getting back the correct stdin. Could someone please direct me here? I feel like I'm going in circles over something silly I'm doing wrong or looking over.

Any help is greatly GREATLY appreciated.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

空城旧梦 2024-10-26 04:22:07

如果要写入文件,为什么要使用 O_RDONLY?我的猜测是你应该使用类似的东西:

int output_fd = open(filename, O_WRONLY|O_CREAT, 0666);

(0666是在创建时设置访问权限)。

显然,如果您无法打开受限制的文件,则不应启动该命令。

Why do you use O_RDONLY if you want to write to the file? My guess is that you should use something like:

int output_fd = open(filename, O_WRONLY|O_CREAT, 0666);

(The 0666 is to set up the access rights when creating).

And obviously, if you can't open a redicted file, you shouldn't launch the command.

梦在夏天 2024-10-26 04:22:07

首先,我注意到的明显的事情是您已经打开了文件 O_RDONLY。输出效果不太好!

其次,重定向输出的基本过程是:

  • 打开文件以写入
  • dup stdout,以便您可以在需要时保留副本。如果重定向则与 stderr 相同。
  • fcntl 将您的副本复制到 CLOEXEC (或者使用 dup3)
  • dup2 文件来 stdout
  • exec 命令

,最后,您真的将命令名称作为全局变量传递吗?我认为一旦你尝试实现 cat foo | ,这个问题就会再次困扰你。 (猫栏;回声嗨;猫)> baz 或类似的东西。

First, obvious thing I notice is that you've opened the file O_RDONLY. Not going to work so well for output!

Second, basic process for redirecting the output is:

  • open file for writing
  • dup stdout so you can keep a copy if needed. same with stderr if redirecting.
  • fcntl your duplicate to CLOEXEC (alternatively, use dup3)
  • dup2 file to stdout
  • exec the command

and finally, are you really passing around command names as global variables? I think this will come back to haunt you once you try and implement cat foo | ( cat bar; echo hi; cat ) > baz or somesuch.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文