Learn › Bash Redirection and File Descriptors › Pipes, redirection precedence, and process ownership
Pipes and process ownership. Try the examples, inspect their output, and explain the result. Practice feedback does not award XP or mastery.
A report reaches its saved file, but the command after the pipe receives nothing. In another script, a loop counts two records and the final total is still zero. Both problems become easier when you separate a byte connection from the shell environment that owns a variable.
A pipeline connects one command's standard output to the next command's standard input. Its commands can run concurrently. A subshell environment starts with copies of shell variables; changing those copies does not update the parent shell.
Use Bash 5.2 or later in this chapter's private practice directory. Each example supplies its own input. Files named here are disposable. Run the multiline examples as complete blocks, so their setup and inspection stay together.
In your managed Bash practice terminal
Run camp-redirection 07 to enter this chapter's private directory. While this lab machine is running, the same command returns to it after reconnecting; camp-redirection 07 --fresh creates a new one without deleting earlier files. Run the code blocks there and use cd to leave.
Bash prepares the pipe connection before applying the command's explicit redirects. Therefore, a later output redirect can replace that command's connection to the pipe.
Predict whether wc receives the line and where alpha will be saved. Track the pipe connection first, then the explicit file redirect.
printf 'alpha\n' > diverted.txt | wc -c
printf 'file: '
cat diverted.txt
Expected output
0
file: alpha
| Process and moment | Descriptor | Destination |
|---|---|---|
| Producer after pipe setup | 1 | Pipe write end |
Producer after > diverted.txt | 1 | Open diverted.txt output |
| Consumer | 0 | Pipe read end |
The producer no longer writes to that pipe. Once every writer reference closes, the consumer reaches end of input. The redirect succeeded; the assumption that output would reach both destinations was wrong. Use tee when copying to a file and forwarding to a consumer is the actual requirement.
The Bash operator |& includes an implicit 2>&1 after the producer's explicit redirects. This can produce a surprising result when stdout has already moved to a file.
Check merged.txt and the descriptor table: |& duplicates stderr only after the explicit stdout redirect, so neither line reaches wc.
probe() {
printf 'report\n'
printf 'warning\n' >&2
}
probe > merged.txt |& wc -c
cat merged.txt
Expected output
0
report
warning
| Producer moment | Descriptor 1 | Descriptor 2 |
|---|---|---|
| Pipe prepared | Pipe write end | Inherited stderr |
| Explicit redirect applied | Open merged.txt output | Inherited stderr |
Implicit 2>&1 applied | Open merged.txt output | Same open output as descriptor 1 |
Neither stream reaches the pipe. With ordinary |, stderr would retain its inherited destination. The two lines in this file have a fixed order because the probe performs two sequential Bash builtin writes.
A while loop repeats its body while its test succeeds. Here, read succeeds once per newline-terminated record. IFS= prevents trimming, and -r prevents backslash interpretation.
This example turns lastpipe off in its isolated subshell. The loop reads two records, but parent count stays 0 because the loop changed a separate copy.
(
shopt -u lastpipe
count=0
printf 'oak\npine\n' | while IFS= read -r tree; do
count=$((count + 1))
done
printf 'parent count: %s\n' "$count"
)
Expected output
parent count: 0
The explicit option setting makes this example independent of your shell's usual configuration. The outer parentheses contain that option change; the loop itself runs in a separate pipeline environment inside them. Its updates disappear before the outer subshell prints the count.
Redirecting a file into the loop avoids making the loop a pipeline component.
printf 'oak\npine\n' > trees.txt
count=0
while IFS= read -r tree; do
count=$((count + 1))
done < trees.txt
printf 'parent count: %s\n' "$count"
The result is parent count: 2. A redirect on a loop supplies input to the whole loop. It does not require a separate shell environment. This is a useful recovery when the next command needs the accumulated value.
Bash's lastpipe option can run the last foreground pipeline component in the current shell when job control is inactive. Job control is the facility used to manage stopped and background jobs, commonly enabled in interactive terminals.
(
shopt -s lastpipe
set +m
value=before
printf 'after\n' | read -r value
printf 'job control off: %s\n' "$value"
set -m
value=before
printf 'after\n' | read -r value
printf 'job control on: %s\n' "$value"
)
The values are after and before, respectively. Parentheses contain the option changes. Do not turn off a learner's interactive job control merely to repair a script. Prefer an explicit file-fed loop when that matches the task.
Try this before reading the solution: create a file containing open, closed, and open, one per line. Count only open records and print the total after the loop. Your total must be two with either setting of lastpipe.
printf 'open\nclosed\nopen\n' > states.txt
count=0
while IFS= read -r state; do
if [[ $state == open ]]; then
count=$((count + 1))
fi
done < states.txt
printf 'open: %s\n' "$count"
This worked solution prints open: 2. [[ ... ]] is Bash's conditional expression syntax. The test chooses records; input redirection keeps their counter in the caller. If your result is zero, inspect process ownership before changing the arithmetic.
Explain both original failures using a descriptor table and a variable owner. Then change the input to include three open records and one blank line. Confirm the result from the data, without changing a hardcoded expected count in your solution.
Reference: GNU Bash pipeline rules.
Practice Chapter 7. Pipes and process ownership in a real Linux terminal at The Linux Camp. Progress is verified automatically as you type commands on the machine.