How to redirect printf() to file and then back to console

后端 未结 2 1492
走了就别回头了
走了就别回头了 2021-01-16 18:54

I am working on a project that requires me to have output from within a mini shell on my C program to output to a file. Using ./program > file.txt wi

2条回答
  •  小鲜肉
    小鲜肉 (楼主)
    2021-01-16 19:25

    Normal shell with fork and exec

    If you have a normal shell where you're using fork() and execvp() or one of its relatives, then you should fork the child process and in the child handle the I/O redirection (with open(), dup2() or dup(), and close()), leaving the parent's I/O unchanged.

    Assuming you have parsed the command into:

    char *argv[] = { "simple-command", "arg1", "arg2", 0 };
    

    and the standard output file name into:

    char *file1 = "filename";
    

    then:

    if ((pid = fork()) < 0)
        ...report error...
    else if (pid == 0)
    {
        int fd = open(file1, O_WRONLY | O_CREAT | O_TRUNC, 0644);
        if (fd < 0)
            ...report error and exit...
        dup2(fd, STDOUT_FILENO);
        close(fd);
        execvp(argv[0], argv);
        ...report error and exit...
    }
    else
    {
        /* Do whatever the parent needs to do */
    }
    

    Note that the parent does not need to change its standard output at all.

    You can also use the sequence:

    close(STDOUT_FILENO);
    int fd = open(file1, O_WRONLY | O_CREAT | O_TRUNC, 0644); // Or 0666
    if (fd < 0)
        ...report error and exit...
    if (fd != STDOUT_FILENO)
        ...standard input must have been closed too...
    

    Redirecting built-in commands without fork and exec

    Quite apart from redirecting in the wrong process, using freopen() or perhaps fdopen() and reopening /dev/stdout won't work. The file that /dev/stdout points to changes as the program is run and files are opened or closed as the main program changes what its file descriptor 1 (standard output) points at.

    If you really need to redirect standard output of the main shell for a built-in, where the fork() and exec()-family functions are not used, then you need to do the equivalent of:

    fflush(stdout);  // Safety precaution
    int fd1 = open(file1, O_WRONLY | O_CREAT | O_TRUNC, 0644);
    if (fd1 < 0)
        ...report error and do not continue...
    int fd2 = dup(STDOUT_FILENO);
    if (fd2 < 0)
        ...report error and do not continue...
    if (dup2(fd2, STDOUT_FILENO) < 0)
        ...report error and do not continue...
    close(fd1);  // check for error?
    ...do whatever operations need standard output redirected...
    fflush(stdout);  // Safety precaution
    if (dup2(fd1, STDOUT_FILENO) < 0)  // Bug fixed!
        ...report error and do not continue...
    close(fd2);
    

    Working code

    I'm sorry, I'm not fully understanding this code (new to C). Can you give a full example?

    OK. I've foregone the generality of a pointer to function for the built-in command which I first coded; I concluded it would probably confuse you more. I decline to do without a 'print an error' function (though I'd often beef it up to report on errno with strerror() as well as printing the basic error message).

    I called the source file simpleshell.c and the program, therefore, simpleshell, but that's over-claiming what it does.

    #include 
    #include 
    #include 
    #include 
    
    static int err_report(const char *fmt, ...);
    
    static int redirect_echo_to_file(char **argv, char *file)
    {
        /* Connect standard output to given file */
        fflush(stdout);
        int fd1 = open(file, O_WRONLY | O_CREAT | O_TRUNC, 0644);
        if (fd1 < 0)
            return err_report("Failed to open %s for writing\n", file);
        int fd2 = dup(STDOUT_FILENO);
        if (fd2 < 0)
            return err_report("Failed to duplicate standard output\n");
        if (dup2(fd1, STDOUT_FILENO) < 0)
            return err_report("Failed to duplicate %s to standard output\n", file);
        close(fd1);
    
        /* Write to standard output */
        char *pad = "";
        while (*argv != 0)
        {
            printf("%s%s", pad, *argv++);
            pad = " ";
        }
        putchar('\n');
    
        /* Reconnect original standard output */
        fflush(stdout);
        if (dup2(fd2, STDOUT_FILENO) < 0)
            return err_report("Failed to reinstate standard output\n");
        close(fd2);
        return 0;
    }
    
    int main(void)
    {
        char *file1 = "file.01";
        char *file2 = "file.02";
        char *arguments[] = { "Hello", "world!", "This", "is your", "echo", "speaking." };
        printf("This is the surrogate shell at work\n");
        printf("Echo the same message to two different files (%s and %s)\n", file1, file2);
        if (redirect_echo_to_file(arguments, file1) != 0 ||
            redirect_echo_to_file(arguments, file2) != 0)
            return -1;
        printf("Regular shell output on standard output.\n");
        printf("Please check %s and %s for a special message.\n", file1, file2);
        return 0;
    }
    
    static int err_report(const char *fmt, ...)
    {
        va_list args;
        va_start(args, fmt);
        vfprintf(stderr, fmt, args);
        va_end(args);
        return -1;
    }
    

    Sample output

    $ ./simpleshell
    This is the surrogate shell at work
    Echo the same message to two different files (file.01 and file.02)
    Regular shell output on standard output.
    Please check file.01 and file.02 for a special message.
    $ cat file.01
    Hello world! This is your echo speaking.
    $ cat file.02
    Hello world! This is your echo speaking.
    $ 
    

提交回复
热议问题