Learn › Bash Redirection and File Descriptors › Follow the descriptors

Chapter 1. Follow the descriptors

Trace stdout and stderr, inspect exact bytes, and build a split/merge/swap wrapper. Public practice checks give feedback; they do not certify mastery.

Follow the descriptors

A report command prints useful data and a warning. Your job is to decide where each one goes, without changing the command itself.

You already know >, 2>, and a pipe from Linux Foundations. Here you will explain their behavior using the objects the shell changes: file descriptors. A descriptor is a small number that a process uses to refer to an open input or output.

By the end you can trace redirect order, exchange two streams using a spare descriptor, and preserve a command's failure status while routing its output.

Run camp-redirection 01 in the managed Bash terminal. It enters a private directory with probe.sh, editable route-report.sh, starter/, solutions/, and check.py. While this lab machine is running, the same command returns to this directory after reconnecting; camp-redirection 01 --fresh creates a new one without deleting earlier files. Run the code blocks there and use cd to leave.

Meet the two-stream probe

The file probe.sh prints one line on standard output and one on standard error. A stream is a sequence of bytes. The names describe intended use; the kernel does not know whether a line is a report or a warning.

Before running it, predict what the terminal will display. Then save its status immediately and explain why seeing two lines does not establish success.

Try in the practice terminal: run the probe

bash probe.sh

Expected result

REPORT sample
WARN sample

The probe uses Bash's printf builtin. It writes the report first and warning second. Its final exit status is 7, a deliberately unsuccessful result. The text alone does not tell you whether a command succeeded.

Immediately after running it, printf '%s\n' "$?" prints 7. $? is the most recent command's status. Running another command first would replace that value.

Give each stream a number

The conventional descriptors are 0 for standard input, 1 for standard output, and 2 for standard error. An ordinary interactive command initially inherits these from the shell, often connected to your terminal.

DescriptorNamePurpose in this probe
0stdinNot read
1stdoutReport line
2stderrWarning line

2>&1 means “make descriptor 2 refer to the destination currently used by descriptor 1.” It does not mean “keep following descriptor 1 whenever it changes.”

A descriptor belongs to a process. Later lessons distinguish that process's descriptor table from the shared open file description that holds information such as a file offset. For now, track the current destination.

Separate the report and warning

With > report.log, the omitted descriptor number is 1. With 2> errors.log, the number selects descriptor 2. These operators are shell syntax, not arguments passed to probe.sh.

The probe writes normal output to report.log and its warning to errors.log. These redirects choose destinations; they do not change the program's exit status.

bash probe.sh > report.log 2> errors.log

Expected output

The command prints nothing here. Inspect its output files in the following steps.

Silence here means neither stream was sent to the terminal. It does not turn the probe's status 7 into success.

Read the separated streams

Try in the practice terminal: read the report

cat report.log

Expected result

REPORT sample

cat errors.log prints WARN sample. Each file ends with one newline.

Merge after opening the file

The shell processes the following redirects from left to right.

Compare the descriptor table before and after each redirect: both probe lines should end in combined.log.

bash probe.sh > combined.log 2>&1

Expected output

The command prints nothing here. Inspect its output files in the following steps.

MomentDescriptor 1Descriptor 2
Before redirectsTerminalTerminal
After > combined.logcombined.logTerminal
After 2>&1combined.logSame open output as descriptor 1

Inspect the merged bytes

Try in the practice terminal: inspect the combined file

cat combined.log

Expected result

REPORT sample
WARN sample

This order is exact for this probe. Different programs can buffer their output or write concurrently, so a merged log does not generally promise the order you expected from the program's source.

Reverse the order and inspect the difference

Use the table to locate the warning: stderr keeps its original terminal destination while stdout goes to report-only.log.

bash probe.sh 2>&1 > report-only.log

Expected output

WARN sample
MomentDescriptor 1Descriptor 2
Before redirectsTerminalTerminal
After 2>&1TerminalSame terminal destination
After > report-only.logreport-only.logOriginal terminal destination

Check the reversed result

Try in the practice terminal: read the remaining report

cat report-only.log

Expected result

REPORT sample

You can now diagnose a common symptom: a log file looks clean while warnings appear in the scheduler or parent terminal. Draw the destinations after each redirect before changing the command.

Notice when files are opened

The redirects are prepared before the command starts. Opening an existing ordinary file with > truncates it to zero bytes. A later redirect can fail after that truncation has already happened.

Use a private practice folder for this example.

printf 'previous report\n' > sacrificial.log
bash probe.sh > sacrificial.log < missing/input.txt

If missing/input.txt does not exist, the shell cannot open the input. The probe never starts, but sacrificial.log has already been emptied. Check with wc -c < sacrificial.log; the result is 0.

The exact diagnostic prefix depends on the shell and invocation. The important evidence is the failed open, the absence of probe output, and the now-empty file.

The safer pattern is to write a separate temporary file, verify success, and only then replace the destination. Chapter 12 develops that pattern in detail. Do not use this demonstration on a file you need to keep.

Whitespace can change the meaning

2> file selects descriptor 2. In printf '%s\n' 2 > number.log, the separated 2 is an argument; only standard output is redirected.

Read number.log and confirm it contains 2; the separated 2 is printf's argument, not a descriptor number.

printf '%s\n' 2 > number.log

Expected output

The command prints nothing here. Inspect its output files in the following steps.

Reading number.log prints 2. Operator adjacency is part of the grammar. Adding spaces indiscriminately can change which bytes are written and where.

Save a destination before replacing it

To exchange stdout and stderr, you need somewhere to save one original destination. This command uses descriptor 3 temporarily:

bash probe.sh 3>&1 1>&2 2>&3 3>&-
RedirectEffect
3>&1Save the original stdout destination on descriptor 3
1>&2Send stdout to the original stderr destination
2>&3Send stderr to the saved original stdout destination
3>&-Close the temporary descriptor after copying it

Both original streams often point to the same terminal, so running this alone can look unchanged. We need an outer observer with different destinations to see the exchange.

This example deliberately reserves descriptor 3 within one command. Larger scripts should track ownership and avoid overwriting a descriptor supplied by their caller. A later lesson introduces Bash's allocated descriptor variables.

Make the exchange visible

Parentheses run the enclosed command in a subshell, a separate shell environment. The outer redirects give that environment distinct files for stdout and stderr. The inner command then exchanges those destinations.

Read outer-stdout.log and outer-stderr.log separately to see the original warning and report after the descriptor swap.

( bash probe.sh 3>&1 1>&2 2>&3 3>&- ) > outer-stdout.log 2> outer-stderr.log

Expected output

The command prints nothing here. Inspect its output files in the following steps.

Inspect the exchanged bytes

Try in the practice terminal: read what reached the outer stdout

cat outer-stdout.log

Expected result

WARN sample

cat outer-stderr.log prints REPORT sample. The exchange changed the destinations, not the bytes and not the descriptor numbers the probe writes to.

Keep the failure status

Routing output and handling status are separate jobs. This pattern remembers the probe's status before any later command can overwrite it:

bash probe.sh > report.log 2> errors.log
status=$?
printf 'probe status: %s\n' "$status"

The assignment reads the preceding status. It then becomes a command of its own, so later uses should read $status, not assume $? still refers to the probe.

In a wrapper script, exit "$status" returns that result to the caller. If a probe invocation is the final command in a script, its status is already the script's status. Adding a successful logging command afterward can silently change it to zero.

You do not need set -e for this exercise. Later lessons show where automatic exit rules apply, where they do not, and how to state a failure policy explicitly.

Build your own routing wrapper

Create your working copy with cp starter/route-report.sh route-report.sh, then open route-report.sh in your preferred editor. Copy the starter once: repeating that command would replace your work. It is an incomplete Bash script with this interface:

bash route-report.sh MODE OUTPUT_DIRECTORY

Complete three modes:

The probe path comes from $LC_PROBE. Invoke it using bash "$LC_PROBE". Quote the output directory when constructing filenames because paths can contain spaces.

All modes must preserve the probe's exit status, including a nonzero status. Do not turn a diagnostic into success or hardcode the sample text.

Ready to practice

Run the public checker from this project:

python3 check.py route-report.sh

It runs your script with several probe payloads and exit statuses in fresh temporary directories. It reports which behavior needs attention and removes only the temporary directories it created. You can rerun it after each edit.

The checker is a local learning tool, not a hidden assessment or a security sandbox. Read the project README.md for prerequisites, Linux runtime requirements, and execution limits. solutions/route-report.sh is available when you need an explanation to compare against your work.

Before moving on, explain why these two commands differ without executing them:

command > file 2>&1
command 2>&1 > file

Your explanation should name the destination of each descriptor after each redirect. Passing the public checks shows that your wrapper handled those cases; it does not certify independent mastery. Chapters 2 through 15 continue with file opening, descriptor lifetime, pipelines and a report-exporter project.

Practice Chapter 1. Follow the descriptors in a real Linux terminal at The Linux Camp. Progress is verified automatically as you type commands on the machine.