Lesson 13 / 25

Redirection and Here-Documents

Redirect standard streams and feed multi-line input to commands.

Three standard streams

Every process starts with three open file descriptors: 0 stdin (input), 1 stdout (normal output) and 2 stderr (errors and diagnostics). Redirection connects them to files: cmd > out.txt writes stdout to a file (truncating it), >> appends, < in.txt reads stdin from a file, 2> err.txt redirects stderr, and 2>&1 sends stderr to wherever stdout currently goes. Order matters: cmd > all.log 2>&1 captures both in the file, while cmd 2>&1 > all.log sends stderr to the terminal. Bash's shortcut &> file redirects both. /dev/null discards output. Scripts should write data to stdout and messages to stderr (echo "warning" >&2) so their output can be piped without mixing in log lines. A here-document (<<EOF ... EOF) feeds multiple lines to a command, with variable expansion; quote the delimiter (<<'EOF') to disable expansion, and use <<-EOF to strip leading tabs. A here-string (<<< "$var") feeds a single string.

stdin, stdout and stderr

Each process has an input and two outputs; redirection points them at files or other processes.

A process box with one arrow entering from the left and two arrows leaving on the right, one plain and one dashed, each pointing at a file icon.
Figure 5.1 — The three standard streams and redirection.

Redirection patterns

Separating, combining and discarding output.

./build.sh > build.log 2>&1          # everything into one file
./build.sh 2> errors.log              # only errors
./build.sh &> /dev/null || echo "build failed" >&2

exec 3> report.txt                    # open fd 3 for writing
echo "header" >&3
echo "row 1"  >&3
exec 3>&-                             # close it

cat > /etc/myapp/config.ini <<EOF
[server]
host = $(hostname)
port = ${PORT:-8080}
EOF

cat <<'EOF'
Literal text: $HOME and $(date) are NOT expanded here
EOF

grep -c error <<< "$log_excerpt"

Errors to stderr, always

If your script prints "Starting..." to stdout, anyone piping its output into another tool gets that line mixed with the data. Send progress and errors to stderr and keep stdout for the actual result.

Quick check: Which command captures both stdout and stderr of build.sh in build.log?

  • ./build.sh > build.log 2>&1
  • ./build.sh 2>&1 > build.log
  • ./build.sh 2> build.log
  • ./build.sh < build.log
Answer

./build.sh > build.log 2>&1 — Redirect stdout to the file first, then point stderr at the same place.