Learn › Bash Redirection and File Descriptors › Opening happens before the command runs

Chapter 2. Opening happens before the command runs

Opening happens before the command runs. Try the examples, inspect their output, and explain the result. Practice feedback does not award XP or mastery.

Investigate an empty report

You have a saved report. A later command cannot open its input, yet the report becomes empty. How could that happen before the command writes anything?

In Bash, a file descriptor is a small numbered handle a process uses for input or output. By convention, handle 0 is standard input, 1 is standard output, and 2 is standard error. < file connects handle 0 to a file, > file connects handle 1, and 2> file connects handle 2. Bash prepares these connections from left to right before it runs the command body.

When > opens an existing regular file such as the report below, it truncates it: its length becomes zero. If a later redirect fails, the command body does not run, but Bash does not restore the old contents.

Run camp-redirection 02 in the managed Bash terminal to enter this chapter's private directory. While this lab machine is running, the same command returns to it after reconnecting; camp-redirection 02 --fresh creates a new one without deleting earlier files. Run the code blocks there and use cd to leave.

Returning to a chapter preserves its files. For a clean replay, run camp-redirection 02 --fresh before the setup below instead of deleting evidence from a resumed directory. The missing-input test assumes missing-source.txt and ran.txt do not exist; ran.txt is a marker that would otherwise make a previous run look like this child started.

Observe preparation failure

First watch an ordinary successful open. The first line writes a saved report. The second line replaces it. cat reads it back:

printf 'previous report\n' > report.txt
printf 'new report\n' > report.txt
cat report.txt

The readback should be new report. The second > emptied the old file when Bash opened it, then printf wrote the new line.

Now restore the old result before testing a failure:

printf 'previous report\n' > report.txt

The next example asks Bash to run a child Bash, a separate Bash program. Its first instruction would create ran.txt as a marker containing started. That marker shows whether its command body began. Its next instruction, cat, would copy the input to the report. Confirm that missing-source.txt and ran.txt are absent in this fresh directory before drawing a conclusion from the result. If either exists, use camp-redirection 02 --fresh and repeat the setup that wrote report.txt. In the check below, -e asks whether a path exists and ! means it does not:

if [[ ! -e missing-source.txt && ! -e ran.txt ]]; then
  printf 'fixtures ready\n'
else
  printf 'Use camp-redirection 02 --fresh, then restore report.txt\n'
fi

Continue only when this prints fixtures ready.

Before running the next block, predict: Will report.txt still contain previous report? Will the child command body create ran.txt? Explain which redirect Bash reaches first.

bash -c 'printf "started\n" > ran.txt; cat' \
  > report.txt 2> open-error.log < missing-source.txt
status=$?
printf 'status=%s\n' "$status"

$? is the status of the command that just finished. Save it immediately because each later command replaces it. Status 0 means success here; a nonzero status means this attempt failed.

Trace the opens in order

Inspect the real files in your practice machine:

printf 'report bytes='; wc -c < report.txt
if [[ -e ran.txt ]]; then printf 'marker exists\n'; else printf 'marker absent\n'; fi
cat open-error.log

wc -c counts bytes. The if checks whether the marker exists. Look for status 1, 0 report bytes, and no marker. The diagnostic wording may vary, but it should name missing-source.txt and say it could not be opened. These observations show that the output file was emptied before the input open failed.

Read the order that explains those observations:

Redirect, from left to rightHandle targetedEffect
> report.txt1, outputOpens report.txt and removes its previous bytes.
2> open-error.log2, diagnosticsOpens the diagnostic file; the report stays empty.
< missing-source.txt0, inputOpen fails. The command body does not run; earlier file changes remain.

The absent marker shows that the child Bash's command body did not run. Bash restores the temporary handles afterward, but that cannot put the old report back.

Notice an unused output file

The same preparation rule explains a smaller case. Predict whether first.txt, second.txt, both, or neither will contain fresh report:

printf 'fresh report\n' > first.txt > second.txt
printf 'first bytes='; wc -c < first.txt
printf 'second: '; cat second.txt

You should find 0 bytes in first.txt and fresh report in second.txt. Bash opened and truncated the first file, then connected handle 1 to the second. Only the last output connection receives the command's bytes.

Improve the order, then identify its limit

For a report copied from input, opening the input before the report means a missing input stops Bash before it opens the report for output. Predict what happens to the old report, then check.

Start with a separate saved file. The diagnostic redirect goes first so an open error is recorded. The input redirect comes before the output redirect:

printf 'previous report\n' > preserved.txt
bash -c 'printf "started\n" > safe-ran.txt; cat' \
  2> input-error.log < missing-source.txt > preserved.txt
status=$?
printf 'status=%s\n' "$status"
cat preserved.txt
if [[ -e safe-ran.txt ]]; then printf 'marker exists\n'; else printf 'marker absent\n'; fi

Look for status 1, previous report, and an absent marker. The missing input caused the failure, so we moved that open before the one that could empty the saved report. Read input-error.log to confirm which open failed.

Now verify that the same order still works when input exists:

printf 'complete report\n' > source.txt
bash -c 'printf "started\n" > success-ran.txt; cat' \
  2> success-error.log < source.txt > preserved.txt
status=$?
printf 'status=%s\n' "$status"
cat preserved.txt
cat success-ran.txt

Look for status 0, complete report, and started. success-error.log should be empty. Check status as well as contents: a program can write output and still fail. Opening input first protects against this missing-input failure. It cannot protect an old report when the program starts, writes part of a new report, then fails.

Keep incomplete output separate

Here is that limit. The child writes partial report and then exits with status 7. Predict what happens to current.txt when it writes there directly:

printf 'last good report\n' > current.txt
bash -c 'printf "partial report\n"; exit 7' > current.txt
status=$?
printf 'status=%s\n' "$status"
cat current.txt

You should find status 7 and partial report in the file. Changing redirect order cannot repair a failure after writing begins.

Keep a candidate file separate while the producer works. mktemp creates it in this practice directory. if takes its first branch only when the producer succeeds; otherwise else saves the failed status and removes the candidate. mv replaces the old report only after success:

(
printf 'last good report\n' > current.txt
candidate=$(mktemp ./candidate.XXXXXX) || exit 1
if bash -c 'printf "partial report\n"; exit 7' > "$candidate"; then
  mv -- "$candidate" current.txt
else
  status=$?
  printf 'producer failed: %s\n' "$status"
  rm -- "$candidate"
fi
cat current.txt
)

You should see producer failed: 7, followed by last good report. The subshell contains the possible early exit and leaves your managed prompt usable. The failed attempt never replaced current.txt. Chapter 12 adds validation and handles other replacement failures.

Repair a different failure

A producer writes unfinished and exits 9. If its output goes directly to approved.txt, what happens to an existing approved line? Use the candidate scaffold above to explain what you would change. Identify the line that must target the candidate. If you try it, check status 9, the saved line, and whether a candidate remains. Chapter 12, *Safer file creation and replacement*, returns to this problem with new constraints.

Ready to practice

The two failures need different repairs. With missing input, Bash's earlier output open erased the report before the command body ran; input-first order prevented that damage. With partial output, the program ran and wrote before failing; a candidate kept the failed result away from the old report.

For each case, compare status, file contents, diagnostic, and marker or candidate cleanup. Next, Chapter 3 asks how long a redirection lasts. Chapter 12 returns to safe replacement after several other mechanisms.

Practice Chapter 2. Opening happens before the command runs in a real Linux terminal at The Linux Camp. Progress is verified automatically as you type commands on the machine.