Redirection
Processes
COP-3402
Table of Contents
Recall: redirection in bash
We can use bash to change (redirect) where stdio goes to and comes from.
cat > outfile.txt
grep define < /usr/include/stdio.h
How does bash implement this? What's the role of standard I/O in making this possible?
Recall: file I/O
- open() - open a file for I/O
- read() - read bytes from an open file
- write() - write bytes from an open file
After we open a file, how do we reference that open file?
File descriptors
- Index into kernel table of open files
- Returned by open
- Used in calls to read/write
Writing to standard out
write_stdout.c
#include <fcntl.h> // O_RDONLY, open
#include <unistd.h> // read, write
#include <stdlib.h> // exit
#include <stdio.h> // perror, fprintf
#include <string.h> // strlen
int main(int argc, char **argv) {
char *msg1 = "hello, world!\n";
ssize_t msg1c = strlen(msg1);
if (-1 == write(STDOUT_FILENO, msg1, msg1c)) {
perror("write");
exit(EXIT_FAILURE);
}
}
What's the difference between stdout and STDOUT_FILENO?
printf vs. write?
Why don't we have to open STDOUT_FILENO?
Duplicate a file descriptor
man 2 dup # LPI chapter 5
Copies the file descriptor, so that we have two.
Both can write to the same open file.
duplicate_stdout.c
#include <fcntl.h> // O_RDONLY, open
#include <unistd.h> // dup, dup2, read, write
#include <stdlib.h> // exit
#include <stdio.h> // perror, fprintf
#include <string.h> // strlen
int main(int argc, char **argv) {
char *msg1 = "hello, world!\n";
ssize_t msg1c = strlen(msg1);
if (-1 == write(STDOUT_FILENO, msg1, msg1c)) {
perror("write");
exit(EXIT_FAILURE);
}
int dupfd = dup(STDOUT_FILENO);
if (-1 == dupfd) {
perror("dup");
exit(EXIT_FAILURE);
}
sleep(2);
char *msg2 = "goodbye!\n";
ssize_t msg2c = strlen(msg2);
if (-1 == write(dupfd, msg2, msg2c)) {
perror("write");
exit(EXIT_FAILURE);
}
}
(Optional) duplicate_fd.c
#include <fcntl.h> // O_RDONLY, open
#include <unistd.h> // dup, dup2, read, write
#include <stdlib.h> // exit
#include <stdio.h> // perror, fprintf
#include <string.h> // strlen
int main(int argc, char **argv) {
char *path = "dupfd.out";
int fd = open(path, O_RDWR | O_TRUNC | O_CREAT, 0644);
if (-1 == fd) {
perror("open");
exit(1);
}
char *msg1 = "hello, world!\n";
ssize_t msg1c = strlen(msg1);
if (-1 == write(fd, msg1, msg1c)) {
perror("write");
exit(EXIT_FAILURE);
}
int dupfd = dup(fd);
if (-1 == dupfd) {
perror("dup");
exit(EXIT_FAILURE);
}
char *msg2 = "goodbye!\n";
ssize_t msg2c = strlen(msg2);
if (-1 == write(fd, msg2, msg2c)) {
perror("write");
exit(EXIT_FAILURE);
}
}
This is the same as duplicating stdout, but in this case we open a file and duplicate that open file's descriptor.
What use is dup?
Redirection is one use!
The dup2 variant
dup2replaces an existing file descriptor
man 2 dup2 # LPI chapter 5
What are the file descriptors for stdin, stdout, and stderr?
Standard I/O file descriptors
/usr/include/unistd.h
#define STDIN_FILENO 0 /* Standard input. */ #define STDOUT_FILENO 1 /* Standard output. */ #define STDERR_FILENO 2 /* Standard error output. */
Replacing a file descriptor
dup2(oldfd, newfd)
To replace stdout:
dup2(oldfd, STDOUT_FILENO)
redirect_stdout.c
#include <fcntl.h> // O_RDONLY, open
#include <unistd.h> // dup, dup2, read, write
#include <stdlib.h> // exit
#include <stdio.h> // perror, fprintf
#include <string.h> // strlen
int main(int argc, char **argv) {
char *msg1 = "write to original stdout\n";
ssize_t msg1c = strlen(msg1);
if (-1 == write(STDOUT_FILENO, msg1, msg1c)) {
perror("write");
exit(EXIT_FAILURE);
}
char *path = "redirect_stdout.out";
int fd = open(path, O_RDWR | O_TRUNC | O_CREAT, 0644);
if (-1 == fd) {
perror("open");
exit(1);
}
if (-1 == dup2(fd, STDOUT_FILENO)) {
perror("dup2");
exit(EXIT_FAILURE);
}
char *msg2 = "written to redirected stdout\n";
ssize_t msg2c = strlen(msg2);
if (-1 == write(STDOUT_FILENO, msg2, msg2c)) {
perror("write");
exit(EXIT_FAILURE);
}
printf("where will printf go now?\n");
}
Why does printf now go to the file instead of the terminal?
(Diagram)
- bash creates process for redirect_stdout, giving it the terminal as stdout
- redirect_stdout writes to stdout (the terminal)
- redirect_stdout opens a new file (redirect_stdout.out)
- redirect_stdout redirects stdout to that file (dup2)
- redirect_stdout writes to stdout, which is now the file
(Optional) Redirecting stdin
#include <fcntl.h> // O_RDONLY, open
#include <unistd.h> // dup, dup2, read, write
#include <stdlib.h> // exit
#include <stdio.h> // perror, fprintf
#include <string.h> // strlen
int main(int argc, char **argv) {
pid_t pid;
if (argc < 2) {
printf("USAGE: %s file_to_read\n", argv[0]);
exit(EXIT_FAILURE);
}
char *path = argv[1];
const int BUFSIZE = 10;
char buf[BUFSIZE];
ssize_t bytes = read(STDIN_FILENO, buf, BUFSIZE);
if (-1 == bytes) {
perror("read");
exit(1);
}
printf("bytes from original stdin: %ld\n", bytes);
for (int i = 0; i < bytes; i++) {
putchar(buf[i]);
}
putchar('\n');
int fd = open(path, O_RDONLY);
if (-1 == fd) {
perror("open");
exit(1);
}
if (-1 == dup2(fd, STDIN_FILENO)) {
perror("dup2");
exit(EXIT_FAILURE);
}
bytes = read(STDIN_FILENO, buf, BUFSIZE);
if (-1 == bytes) {
perror("read");
exit(1);
}
printf("bytes from redirected stdin: %ld\n", bytes);
for (int i = 0; i < bytes; i++) {
putchar(buf[i]);
}
putchar('\n');
}
Redirecting a child process's I/O
child.c
#include <fcntl.h> // O_RDONLY, open
#include <unistd.h> // dup, dup2, read, write, fork
#include <stdlib.h> // exit
#include <stdio.h> // perror, fprintf
#include <string.h> // strlen
#include <inttypes.h> // intmax_t
#include <sys/wait.h> // wait
int main(int argc, char **argv) {
pid_t pid;
if (argc < 2) {
printf("USAGE: %s file_to_write\n", argv[0]);
exit(EXIT_FAILURE);
}
char *path = argv[1];
int fd = open(path, O_RDWR | O_TRUNC | O_CREAT, 0644);
if (-1 == fd) {
perror("open");
exit(1);
}
switch (pid = fork()) {
case -1:
perror("fork");
exit(EXIT_FAILURE);
break;
case 0:
// child
if (-1 == dup2(fd, STDOUT_FILENO)) {
perror("dup2");
exit(EXIT_FAILURE);
}
printf("child process\n");
exit(EXIT_SUCCESS);
break;
default:
// parent
printf("parent process\n");
exit(EXIT_SUCCESS);
break;
}
}
child_exec.c
#include <fcntl.h> // O_RDONLY, open
#include <unistd.h> // dup, dup2, read, write, fork
#include <stdlib.h> // exit
#include <stdio.h> // perror, fprintf
#include <string.h> // strlen
#include <inttypes.h> // intmax_t
#include <sys/wait.h> // wait
int main(int argc, char **argv) {
pid_t pid;
if (argc < 2) {
printf("USAGE: %s file_to_write\n", argv[0]);
exit(EXIT_FAILURE);
}
char *path = argv[1];
int fd = open(path, O_RDWR | O_TRUNC | O_CREAT, 0644);
if (-1 == fd) {
perror("open");
exit(1);
}
switch (pid = fork()) {
case -1:
perror("fork");
exit(EXIT_FAILURE);
break;
case 0:
// child
if (-1 == dup2(fd, STDOUT_FILENO)) {
perror("dup2");
exit(EXIT_FAILURE);
}
char *prog = "/usr/bin/ls";
char *newargv[] = { prog, "/", NULL };
char *newenv[] = { NULL };
execve(prog, newargv, newenv);
perror("execve");
_exit(EXIT_FAILURE);
break;
default:
// parent
printf("parent process\n");
exit(EXIT_SUCCESS);
break;
}
}