Learn › Bash Redirection and File Descriptors › 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.
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.
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.
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.
| Descriptor | Name | Purpose in this probe |
|---|---|---|
| 0 | stdin | Not read |
| 1 | stdout | Report line |
| 2 | stderr | Warning 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.
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.
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.
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.
| Moment | Descriptor 1 | Descriptor 2 |
|---|---|---|
| Before redirects | Terminal | Terminal |
After > combined.log | combined.log | Terminal |
After 2>&1 | combined.log | Same open output as descriptor 1 |
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.
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
| Moment | Descriptor 1 | Descriptor 2 |
|---|---|---|
| Before redirects | Terminal | Terminal |
After 2>&1 | Terminal | Same terminal destination |
After > report-only.log | report-only.log | Original terminal destination |
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.
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.
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.
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>&-
| Redirect | Effect |
|---|---|
3>&1 | Save the original stdout destination on descriptor 3 |
1>&2 | Send stdout to the original stderr destination |
2>&3 | Send 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.
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.
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.
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.
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:
split: run the supplied probe; put stdout in report.log and stderr in errors.log inside the existing output directory. Leave the wrapper's own stdout and stderr empty when the files open successfully.merge: run the probe; put both streams in combined.log. Leave the wrapper's own streams empty when the file opens successfully.swap: run the probe; return its stdout on the wrapper's stderr, and its stderr on the wrapper's stdout. No output file is needed.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.
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.