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
dup2
replaces 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; } }