|
| 1 | +#lang pollen |
| 2 | + |
| 3 | +◊page-init{} |
| 4 | +◊define-meta[page-title]{I/O Redirection Detailed} |
| 5 | +◊define-meta[page-description]{A Detailed Introduction to I/O and I/O Redirection} |
| 6 | + |
| 7 | +written by Stéphane Chazelas, and revised by the document author |
| 8 | + |
| 9 | +A command expects the first three file descriptors to be |
| 10 | +available. The first, ◊code{fd 0} (standard input, stdin), is for |
| 11 | +reading. The other two (◊code{fd 1}, stdout and ◊code{fd 2}, stderr) are for |
| 12 | +writing. |
| 13 | + |
| 14 | +There is a stdin, stdout, and a stderr associated with each |
| 15 | +command. ◊command{ls 2>&1} means temporarily connecting the stderr of |
| 16 | +the ◊command{ls} command to the same "resource" as the shell's stdout. |
| 17 | + |
| 18 | +By convention, a command reads its input from ◊code{fd 0} (stdin), |
| 19 | +prints normal output to ◊code{fd 1} (stdout), and error ouput to |
| 20 | +◊code{fd 2} (stderr). If one of those three fd's is not open, you may |
| 21 | +encounter problems: |
| 22 | + |
| 23 | +◊example{ |
| 24 | +bash$ cat /etc/passwd >&- |
| 25 | +cat: standard output: Bad file descriptor |
| 26 | +} |
| 27 | + |
| 28 | +For example, when ◊command{xterm} runs, it first initializes |
| 29 | +itself. Before running the user's shell, ◊command{xterm} opens the |
| 30 | +terminal device (◊fname{/dev/pts/<n>} or something similar) three |
| 31 | +times. |
| 32 | + |
| 33 | +At this point, Bash inherits these three file descriptors, and each |
| 34 | +command (child process) run by Bash inherits them in turn, except when |
| 35 | +you redirect the command. Redirection means reassigning one of the |
| 36 | +file descriptors to another file (or a pipe, or anything |
| 37 | +permissible). File descriptors may be reassigned locally (for a |
| 38 | +command, a command group, a subshell, a while or if or case or for |
| 39 | +loop...), or globally, for the remainder of the shell (using |
| 40 | +◊command{exec}). |
| 41 | + |
| 42 | +◊command{ls > /dev/null} means running ◊command{ls} with its ◊code{fd |
| 43 | +1} connected to ◊fname{/dev/null}. |
| 44 | + |
| 45 | +◊example{ |
| 46 | +bash$ lsof -a -p $$ -d0,1,2 |
| 47 | +COMMAND PID USER FD TYPE DEVICE SIZE NODE NAME |
| 48 | +bash 363 bozo 0u CHR 136,1 3 /dev/pts/1 |
| 49 | +bash 363 bozo 1u CHR 136,1 3 /dev/pts/1 |
| 50 | +bash 363 bozo 2u CHR 136,1 3 /dev/pts/1 |
| 51 | + |
| 52 | + |
| 53 | +bash$ exec 2> /dev/null |
| 54 | +bash$ lsof -a -p $$ -d0,1,2 |
| 55 | +COMMAND PID USER FD TYPE DEVICE SIZE NODE NAME |
| 56 | +bash 371 bozo 0u CHR 136,1 3 /dev/pts/1 |
| 57 | +bash 371 bozo 1u CHR 136,1 3 /dev/pts/1 |
| 58 | +bash 371 bozo 2w CHR 1,3 120 /dev/null |
| 59 | + |
| 60 | + |
| 61 | +bash$ bash -c 'lsof -a -p $$ -d0,1,2' | cat |
| 62 | +COMMAND PID USER FD TYPE DEVICE SIZE NODE NAME |
| 63 | +lsof 379 root 0u CHR 136,1 3 /dev/pts/1 |
| 64 | +lsof 379 root 1w FIFO 0,0 7118 pipe |
| 65 | +lsof 379 root 2u CHR 136,1 3 /dev/pts/1 |
| 66 | + |
| 67 | + |
| 68 | +bash$ echo "$(bash -c 'lsof -a -p $$ -d0,1,2' 2>&1)" |
| 69 | +COMMAND PID USER FD TYPE DEVICE SIZE NODE NAME |
| 70 | +lsof 426 root 0u CHR 136,1 3 /dev/pts/1 |
| 71 | +lsof 426 root 1w FIFO 0,0 7520 pipe |
| 72 | +lsof 426 root 2w FIFO 0,0 7520 pipe |
| 73 | +} |
| 74 | + |
| 75 | +This works for different types of redirection. |
| 76 | + |
| 77 | +Exercise: Analyze the following script. |
| 78 | + |
| 79 | +◊example{ |
| 80 | +#! /usr/bin/env bash |
| 81 | + |
| 82 | +mkfifo /tmp/fifo1 /tmp/fifo2 |
| 83 | +while read a; do echo "FIFO1: $a"; done < /tmp/fifo1 & exec 7> /tmp/fifo1 |
| 84 | +exec 8> >(while read a; do echo "FD8: $a, to fd7"; done >&7) |
| 85 | + |
| 86 | +exec 3>&1 |
| 87 | +( |
| 88 | + ( |
| 89 | + ( |
| 90 | + while read a; do echo "FIFO2: $a"; done < /tmp/fifo2 | tee /dev/stderr \ |
| 91 | + | tee /dev/fd/4 | tee /dev/fd/5 | tee /dev/fd/6 >&7 & exec 3> /tmp/fifo2 |
| 92 | + |
| 93 | + echo 1st, to stdout |
| 94 | + sleep 1 |
| 95 | + echo 2nd, to stderr >&2 |
| 96 | + sleep 1 |
| 97 | + echo 3rd, to fd 3 >&3 |
| 98 | + sleep 1 |
| 99 | + echo 4th, to fd 4 >&4 |
| 100 | + sleep 1 |
| 101 | + echo 5th, to fd 5 >&5 |
| 102 | + sleep 1 |
| 103 | + echo 6th, through a pipe | sed 's/.*/PIPE: &, to fd 5/' >&5 |
| 104 | + sleep 1 |
| 105 | + echo 7th, to fd 6 >&6 |
| 106 | + sleep 1 |
| 107 | + echo 8th, to fd 7 >&7 |
| 108 | + sleep 1 |
| 109 | + echo 9th, to fd 8 >&8 |
| 110 | + |
| 111 | + ) 4>&1 >&3 3>&- | while read a; do echo "FD4: $a"; done 1>&3 5>&- 6>&- |
| 112 | + ) 5>&1 >&3 | while read a; do echo "FD5: $a"; done 1>&3 6>&- |
| 113 | +) 6>&1 >&3 | while read a; do echo "FD6: $a"; done 3>&- |
| 114 | + |
| 115 | +rm -f /tmp/fifo1 /tmp/fifo2 |
| 116 | + |
| 117 | + |
| 118 | +# For each command and subshell, figure out which fd points to what. |
| 119 | +# Good luck! |
| 120 | + |
| 121 | +exit 0 |
| 122 | +} |
0 commit comments