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
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...
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);
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 <stdarg.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
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;
}
$ ./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.
$
You can use freopen.
freopen("desiredFileName", "w", stdout);
Once you are done, you can undo the reassignment like this:
freopen("/dev/stdout", "w", stdout);
on Unix/Linux systems. I'm not sure whether there is a portable way to revert back.
UPDATE
If possible, you can use fprintf
instead of printf
, providing either stdout
or a handle to the desired file, as appropriate.