Pipes and File Descriptors
Chapter 11: Pipes, Redirection, and File Descriptors Deep Dive
Linux's "everything is a file" philosophy finds its fullest expression in pipes and file descriptors. Understanding how file descriptors work โ how processes read and write data streams through numbered handles 0/1/2, how pipes ferry bytes through kernel buffers โ is the core foundation for writing efficient, reliable Shell scripts. This chapter dissects the complete implementation of redirection and pipes from a kernel perspective.
11.1 File Descriptor Basics: 0 / 1 / 2
A file descriptor (FD) is a non-negative integer index the kernel assigns to each open file or data stream. Every process has its own FD table. Three descriptors are open by default at process startup:
- 0 โ stdin๏ผstandard input, defaults to keyboard
- 1 โ stdout๏ผstandard output, defaults to terminal
- 2 โ stderr๏ผstandard error, defaults to terminal
Process (bash, PID 1234) โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ FD Table โ โ โโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ โ โ 0 โ โ /dev/pts/0 (stdin) โ โ โ โ 1 โ โ /dev/pts/0 (stdout) โ โ โ โ 2 โ โ /dev/pts/0 (stderr) โ โ โ โ 3 โ โ /var/log/app.log (custom) โ โ โ โโโโโโดโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
After: exec 1>app.log โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ FD Table โ โ โโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ โ โ 0 โ โ /dev/pts/0 (stdin) โ โ โ โ 1 โ โ app.log (stdout now!) โ โ โ โ 2 โ โ /dev/pts/0 (stderr) โ โ โ โโโโโโดโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
The kernel exposes a process's FD table through `/proc/PID/fd/`, where each entry is a symlink to the actual file. `lsof -p` presents the same information in a more readable format.
# ๆฅ็ๅฝๅ bash ่ฟ็จ็ๆไปถๆ่ฟฐ็ฌฆ
ls -la /proc/$$/fd
# lrwx------ 1 user user 64 Apr 25 10:00 0 -> /dev/pts/0
# lrwx------ 1 user user 64 Apr 25 10:00 1 -> /dev/pts/0
# lrwx------ 1 user user 64 Apr 25 10:00 2 -> /dev/pts/0
# ๆฅ็ๆไธช่ฟ็จ็ FD๏ผไปฅ PID 1234 ไธบไพ๏ผ
ls -la /proc/1234/fd
# ็จ lsof ๆฅ็่ฟ็จๆๅผ็ๆไปถๆ่ฟฐ็ฌฆ
lsof -p $$
# ๆ่ฟๆปคๅช็ FD ๅ
lsof -p $$ | awk 'NR==1 || $4 ~ /^[0-9]/'
# ๆฅ็ FD ๆฐ้้ๅถ
ulimit -n # ๅฝๅ่ฝฏ้ๅถ๏ผ้ๅธธ 1024๏ผ
cat /proc/sys/fs/file-max # ็ณป็ป็บงๆๅคง FD ๆฐ
11.2 Redirection Operators โ Complete Reference
Redirection is fundamentally about modifying a child process's FD table after fork() but before exec(), pointing standard streams to files instead of the terminal. Shell provides concise syntax to accomplish this kernel operation.
# === ่พๅบ้ๅฎๅ ===
command > file # stdout ่ฆ็ๅๅ
ฅ file๏ผFD1 โ file๏ผ
command >> file # stdout ่ฟฝๅ ๅๅ
ฅ file
command 2> file # stderr ่ฆ็ๅๅ
ฅ file๏ผFD2 โ file๏ผ
command 2>> file # stderr ่ฟฝๅ ๅๅ
ฅ file
# === ๅๅนถ stderr ๅฐ stdout ===
command 2>&1 # ๅฐ FD2 ๅคๅถไธบ FD1 ็ๅฏๆฌ๏ผไธค่
ๆๅๅไธ็ฎๆ ๏ผ
command > file 2>&1 # stdout+stderr ้ฝๅๅ
ฅ file๏ผ้กบๅบ้่ฆ๏ผ๏ผ
command 2>&1 > file # ้่ฏฏๅๆณ๏ผstderr ๅ
ๆๅๆง stdout๏ผ็ป็ซฏ๏ผ๏ผๅ้ๅฎๅ stdout
# bash 4+ ็็ฎๅ๏ผ็ญไปทไบ > file 2>&1๏ผ
command &> file
command &>> file # ่ฟฝๅ ็ๆฌ
# === ไธขๅผ่พๅบ ===
command > /dev/null # ไธขๅผ stdout
command 2> /dev/null # ไธขๅผ stderr
command > /dev/null 2>&1 # ไธขๅผๆๆ่พๅบ
command &> /dev/null # ็ฎๅ
# === ่พๅ
ฅ้ๅฎๅ ===
command ไธ่ฝ่ฆ็ๅทฒๅญๅจ็ๆไปถ
echo "test" > existing.txt # ๆฅ้๏ผcannot overwrite existing file
echo "test" >| existing.txt # ๅผบๅถ่ฆ็๏ผ็ป่ฟ noclobber๏ผ
set +C # ๅ
ณ้ญ noclobber
# === ๅฎ็จ็ปๅ็คบไพ ===
# ๅๆถ่ฎฐๅฝ stdout ๅ stderr ๅฐๅ่ชๆไปถ
command > out.log 2> err.log
# ็ผ่ฏๅนถๅช็้่ฏฏ
make 2>&1 | grep -i error
# ไธขๅผ stdout๏ผๅช็ stderr
command > /dev/null
# ๆต่ฏๅฝไปคๆฏๅฆๆๅ๏ผไธๆพ็คบไปปไฝ่พๅบ๏ผ
if grep -q "pattern" file 2>/dev/null; then
echo "found"
fi
Pitfall: Order of 2>&1
command > file 2>&1andcommand 2>&1 > fileare completely different. Shell processes redirections left to right: the first sets FD1 to file then copies FD2 from FD1 (also file); the second copies FD2 from the current FD1 (terminal) then sets FD1 to file โ stderr still goes to the terminal.
| Operator | Effect | Equivalent |
|---|---|---|
| > file | stdout overwrite | 1> file |
| >> file | stdout append | 1>> file |
| 2> file | stderr overwrite | โ |
| 2>&1 | stderr โ stdout target | โ |
| &> file | stdout+stderr write | > file 2>&1 |
| stdin from file | 0 | |
| > | file | force overwrite (bypass noclobber) |
11.3 Pipe Internals: Kernel Buffer and Subprocesses
The pipe | is one of Unix's greatest inventions. The kernel creates a circular buffer (default 65536 bytes, i.e., 64 KB) for each pipe: the left command's stdout connects to the write end, the right command's stdin connects to the read end. Both commands run concurrently; the kernel coordinates data flow.
# ๅบๆฌ็ฎก้๏ผls ็ stdout โ grep ็ stdin
ls -la | grep ".sh"
# ๅค็บง็ฎก้
cat /var/log/syslog | grep "error" | sort | uniq -c | sort -rn | head -20
# |& ๅๆถไผ ้ stdout ๅ stderr๏ผbash 4+๏ผ
command |& grep "ERROR"
# ็ญไปทไบ๏ผcommand 2>&1 | grep "ERROR"
# ๆฅ็็ฎก้็ผๅฒๅบๅคงๅฐ๏ผLinux ้ป่ฎค 65536 ๅญ่๏ผ
cat /proc/sys/fs/pipe-max-size
# ็ฎก้็ถๆ๏ผๅฝ็ผๅฒๅบๆปกๆถ๏ผๅ็ซฏ้ปๅก๏ผ็ผๅฒๅบ็ฉบๆถ๏ผ่ฏป็ซฏ้ปๅก
# ๅฉ็จ่ฟไธช็นๆงๅฏไปฅๅฎ็ฐ่ๅ๏ผbackpressure๏ผ
# ็ฎก้็้ๅบ็ถๆ้ฎ้ข
ls nonexistent | wc -l # ls ๅคฑ่ดฅ๏ผไฝๆดไธช็ฎก้้ๅบ็ ๆฏ wc ็้ๅบ็ ๏ผ0๏ผ๏ผ
echo $? # 0 โ ๆฉ็ไบ ls ็้่ฏฏ
# ็จ PIPESTATUS ่ทๅ็ฎก้ไธญๆฏไธชๅฝไปค็้ๅบ็ ๏ผbash ไธๆ๏ผ
ls nonexistent | wc -l
echo "${PIPESTATUS[@]}" # ไพ๏ผ2 0 ๏ผls ๅคฑ่ดฅ=2๏ผwc ๆๅ=0๏ผ
# set -o pipefail๏ผ่ฎฉ็ฎก้่ฟๅๆๅณ้้ถ้ๅบ็ ๏ผๆจ่๏ผ๏ผ
set -o pipefail
ls nonexistent | wc -l
echo $? # ็ฐๅจๆฏ 2๏ผls ็้ๅบ็ ๏ผ
Variable Scope in Pipelines
Commands on the right side of a pipe run in a subshell, meaning variables assigned inside the pipeline vanish after it ends. This is one of the most common "lost variable" traps in bash scripts:
# ้ท้ฑ็คบไพ๏ผcount ๅจๅญ shell ไธญ่ตๅผ๏ผ็ถ shell ็ไธ่ง
count=0
echo "a b c" | while read word; do
count=$((count + 1))
done
echo "count=$count" # ่พๅบ๏ผcount=0 โ ๅ้ไธขๅคฑ๏ผ
# ่งฃๅณๆนๆก 1๏ผ็จ่ฟ็จๆฟๆข๏ผ้ฟๅ
็ฎก้ๅญ shell๏ผ
count=0
while read word; do
count=$((count + 1))
done /tmp/count.txt
count=$(cat /tmp/count.txt)
# ๆฃๆฅๅฝๅๆฏๅฆๅจๅญ shell ไธญ
echo "BASH_SUBSHELL=$BASH_SUBSHELL" # 0=ๅฝๅshell๏ผ1=ๅญshell๏ผ2=ๅญๅญshell
(echo "inside subshell: BASH_SUBSHELL=$BASH_SUBSHELL")
11.4 Here-Document: Elegant Multi-Line Input
A here-document (`
11.5 Here-String: Single-Line String to stdin
`
11.6 Process Substitution: ()
Process substitution is an advanced bash/zsh feature that presents a command's output or input as a file path (via /dev/fd/N or /proc/self/fd/N). This solves cases where a command needs two file arguments that pipes cannot satisfy.
# (command)๏ผๅฐๆไปถ่พๅบไผ ็ปๅฝไปค๏ผๅฏๅ๏ผ
# tee ๅๆถๅๅ
ฅๆไปถๅ่ฟไธๆญฅๅค็
tee >(gzip > backup.gz) /dev/null
# ๅๆถๅ้ๅฐไธคไธชๅค็็ฎก้
command | tee >(grep "ERROR" > errors.log) >(grep "WARN" > warnings.log) > /dev/null
# ็ปๅไฝฟ็จ๏ผไป่ฟ็จๆฟๆข่ฏปๅๅนถๅๅ
ฅ่ฟ็จๆฟๆข
cmp **Why Not Temporary Files?** Process substitution beats temporary files on three counts: 1) no manual cleanup needed; 2) data flows in memory without hitting disk (great for large files); 3) concurrent execution โ both sides run simultaneously. The downside: bash/zsh only, not POSIX `sh`.
## 11.7 Manipulating File Descriptors with exec
The `exec` shell builtin can not only replace the current process โ it can directly modify the current shell's FD table without replacing the process. This is the foundation for script-level log redirection and a core technique in advanced shell programming.
```bash
# === ๆๅผ่ชๅฎไนๆไปถๆ่ฟฐ็ฌฆ ===
exec 3>output.txt # ๆๅผ FD3 ็จไบๅ๏ผ่ฆ็๏ผ
exec 4>>append.txt # ๆๅผ FD4 ็จไบ่ฟฝๅ ๅ
exec 5bidirectional.txt # ๆๅผ FD6 ่ฏปๅ๏ผๅฐ็จ๏ผ
# ๅ FD3 ๅๅ
ฅ
echo "line 1" >&3
echo "line 2" >&3
# ไป FD5 ่ฏปๅ
read line &- # ๅ
ณ้ญ FD3๏ผๅ็ซฏ๏ผ
exec 5"$LOGFILE"
exec 2>&1
echo "This goes to $LOGFILE" # ๅๅ
ฅๆฅๅฟ
ls /nonexistent # ้่ฏฏไนๅๅ
ฅๆฅๅฟ
# === ไฟๅญๅนถๆขๅคๅๅง stdout/stderr ===
exec 3>&1 # ๅฐๅฝๅ stdout ไฟๅญๅฐ FD3
exec 4>&2 # ๅฐๅฝๅ stderr ไฟๅญๅฐ FD4
exec 1>/tmp/script.log 2>&1 # ้ๅฎๅ
echo "In log"
ls /nonexistent
exec 1>&3 # ๆขๅค stdout
exec 2>&4 # ๆขๅค stderr
exec 3>&- # ๅ
ณ้ญไธดๆถ FD3
exec 4>&- # ๅ
ณ้ญไธดๆถ FD4
echo "Back to terminal" # ็ฐๅจ่พๅบๅฐ็ป็ซฏ
# === ไฝฟ็จ้ซ็ผๅท FD ้ฟๅ
ๅฒ็ช ===
# bash 4.1+ ๆฏๆ {var} ่ชๅจๅ้
FD๏ผ้ฟๅ
็กฌ็ผ็ ๏ผ
exec {myfd}>output.txt
echo "Using auto FD: $myfd" >&$myfd
exec {myfd}>&- # ๅ
ณ้ญ
Production-Grade Log Redirection Script
#!/usr/bin/env bash
# ็ไบง็บง่ๆฌ๏ผๅๆถ่พๅบๅฐ็ป็ซฏๅๆฅๅฟๆไปถ
# ไฝฟ็จ exec + tee ๅฎ็ฐๅ้่พๅบ
LOGFILE="/var/log/deploy-$(date +%Y%m%d-%H%M%S).log"
mkdir -p "$(dirname "$LOGFILE")"
# ๆๅทง๏ผ็จ tee ๅฐๆๆ่พๅบๅๆถๅ้ๅฐ็ป็ซฏๅๆฅๅฟๆไปถ
exec 1> >(tee -a "$LOGFILE") 2>&1
echo "[$(date '+%Y-%m-%d %H:%M:%S')] === Deploy started ==="
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Log file: $LOGFILE"
# ไปฅไธๆๆ่พๅบ่ชๅจ่ฎฐๅฝๅฐๆฅๅฟ
echo "Step 1: Pulling latest code..."
git pull origin main
echo "Step 2: Installing dependencies..."
npm install --production
echo "Step 3: Restarting service..."
systemctl restart myapp
echo "[$(date '+%Y-%m-%d %H:%M:%S')] === Deploy completed ==="
11.8 Named Pipes (FIFO): Persistent Pipes
Anonymous pipes (|) last only as long as the command, but a named pipe (FIFO) is a special file in the filesystem that lets unrelated processes communicate via a file path. FIFOs use the same kernel buffer but persist by name.
# ๅๅปบๅฝๅ็ฎก้
mkfifo /tmp/mypipe
ls -la /tmp/mypipe
# prw-r--r-- 1 user user 0 Apr 25 10:00 /tmp/mypipe
# 'p' ่กจ็คบ่ฟๆฏ FIFO ๆไปถ
# === ็ไบง่
/ ๆถ่ดน่
ๆจกๅผ ===
# ็ป็ซฏ 1๏ผๆถ่ดน่
๏ผๅ
ๅฏๅจ๏ผไผ้ปๅก็ญๅพ
ๆฐๆฎ๏ผ
cat /tmp/mypipe
# ็ป็ซฏ 2๏ผ็ไบง่
๏ผๅๅ
ฅๆฐๆฎๅๆถ่ดน่
่ชๅจ้ๅบ๏ผ
echo "Hello from producer" > /tmp/mypipe
# === ๅๅฐๆถ่ดน่
+ ๅฎๆถๆฅๅฟ ===
mkfifo /tmp/logpipe
# ๅๅฐ๏ผๆ็ปญ่ฏปๅ็ฎก้ๆฐๆฎๅนถๅๅ
ฅๆไปถ
while true; do
cat /tmp/logpipe >> /var/log/myapp.log 2>/dev/null || break
done &
LOG_READER_PID=$!
# ๅบ็จ็จๅบๅๆฅๅฟๅฐ็ฎก้
echo "App started" > /tmp/logpipe
echo "Processing..." > /tmp/logpipe
# ๆธ
็
kill $LOG_READER_PID
rm /tmp/logpipe
# === ๅฟๅ็ฎก้ vs ๅฝๅ็ฎก้ๅฏนๆฏ ===
# ๅฟๅ็ฎก้๏ผๅช่ฝ็จไบๆไบฒ็ผๅ
ณ็ณป็่ฟ็จ๏ผ็ถๅญๅ
ณ็ณป๏ผ๏ผๅฝไปค่กไธญ่ชๅจๅๅปบ
# ๅฝๅ็ฎก้๏ผไปปๆ่ฟ็จ้ฝๅฏ้่ฟๆไปถ่ทฏๅพ้ไฟก๏ผ้่ฆ mkfifo ๆพๅผๅๅปบ
# ๅ
ฑๅ็น๏ผ้ฝๆฏๅ
ๆ ธ็ผๅฒๅบ๏ผ่ฏปๅๅๆญฅ๏ผๅๅๆตๅจ
# ๅฉ็จ FIFO ๅฎ็ฐ็ฎๅ็่ฟ็จ้ดไฟกๅท
mkfifo /tmp/ready_signal
# ่ฟ็จ A๏ผๅฎๆๅๅงๅๅๅไฟกๅท
echo "ready" > /tmp/ready_signal
# ่ฟ็จ B๏ผ็ญๅพ
ไฟกๅทๅๅผๅงๅทฅไฝ
read signal
## 11.9 tee: Splitting Output Streams
`tee` works like a T-pipe fitting: it reads from stdin and simultaneously writes to stdout and one or more files. This makes it the perfect tool for "tapping into" a pipeline to record data midstream.
```bash
# ๅบๆฌ็จๆณ๏ผ่พๅบๅฐ็ป็ซฏ็ๅๆถไฟๅญๅฐๆไปถ
ls -la | tee file_list.txt
# -a๏ผ่ฟฝๅ ๅ๏ผไธ่ฆ็๏ผ
command | tee -a output.log
# ๅๅ
ฅๅคไธชๆไปถ
command | tee file1.txt file2.txt file3.txt
# ็ฎก้็ปง็ปญๅค็
ls -la | tee /tmp/raw_list.txt | grep "\.sh" | wc -l
# ็ปๅ sudo ๅๅ
ฅ้่ฆๆ้็ๆไปถ๏ผๅธธ่ง็จๆณ๏ผ
echo "new content" | sudo tee /etc/somefile.conf
# ๆณจๆ๏ผไธ่ฝ็จ sudo echo "..." > /etc/somefile๏ผ้ๅฎๅ็ฑๆฎ้็จๆทๆง่ก๏ผ
# tee + ่ฟ็จๆฟๆข๏ผไธไปฝ่พๅบ๏ผๅคไธชๅค็็ฎก้
command | tee >(grep "ERROR" | mail -s "Errors" [email protected]) \
>(grep "WARN" >> warnings.log) \
> full.log
# ๅฎๆถ็ๆงๅนถ่ฎฐๅฝๆฅๅฟ๏ผๆพ็คบๅฐ็ป็ซฏๅๆถๅๆไปถ๏ผ
tail -f /var/log/nginx/access.log | tee -a /tmp/monitoring.log | grep "500"
# ้
ๅๆถ้ดๆณ่ฎฐๅฝๆๅปบๆฅๅฟ
make 2>&1 | tee >(awk '{print strftime("[%H:%M:%S]"), $0}' > build.log)
11.10 /dev Special Files: null / zero / random / tcp
Linux's /dev directory contains virtual device files backed by no physical hardware โ they are special data sources or sinks provided by the kernel. Using them well produces elegant shell scripts.
# === /dev/null โ ้ปๆด่ฎพๅค ===
# ่ฏปๅ๏ผ็ซๅณ่ฟๅ EOF๏ผ็ฉบๆไปถ๏ผ
# ๅๅ
ฅ๏ผไธขๅผๆๆๆฐๆฎ
command > /dev/null 2>&1 # ไธขๅผๆๆ่พๅบ
cat /dev/null > file.txt # ๆธ
็ฉบๆไปถ๏ผๆฏ echo -n > file ๆด่ฏญไนๅ๏ผ
: > file.txt # ๅๆ ทๆๆ๏ผๆด็ฎๆด
# === /dev/zero โ ้ถๅญ่็ๆๅจ ===
# ๆ ้ๆไพๅผไธบ 0 ็ๅญ่๏ผNUL ๅญ็ฌฆ๏ผ
# ๅๅปบๆๅฎๅคงๅฐ็็ฉบๆไปถ๏ผๆฏ fallocate ๆด้็จ๏ผ
dd if=/dev/zero of=emptyfile bs=1M count=100 # ๅๅปบ 100MB ๅ
จ้ถๆไปถ
# ๆธ
้ถๆๆๆฐๆฎๆไปถ๏ผๅฎๅ
จๅ ้คๅ๏ผ
dd if=/dev/zero of=secrets.txt bs=1 count=$(stat -c%s secrets.txt)
# === /dev/random ไธ /dev/urandom โ ้ๆบๆบ ===
# /dev/random๏ผ้ปๅกๅผ๏ผ็ตๆฑ ไธ่ถณๆถ็ญๅพ
๏ผ้ๅๅฏ้ฅ็ๆ๏ผ
# /dev/urandom๏ผ้้ปๅกๅผ๏ผ็ตๆฑ ไธ่ถณๆถ็จไผช้ๆบ๏ผ้ๅไธ่ฌๅบๆฏ๏ผ
# ็ๆ้ๆบๅฏ็ ๏ผ32ๅญ็ฌฆ๏ผbase64็ผ็ ๏ผ
head -c 24 /dev/urandom | base64
# ็ๆ้ๆบๅๅ
ญ่ฟๅถๅญ็ฌฆไธฒ
head -c 16 /dev/urandom | xxd -p | tr -d '\n'
# ็ๆ้ๆบ UUID๏ผๆๅจๅฎ็ฐ๏ผ
cat /proc/sys/kernel/random/uuid # ๆด็ฎๅ็ๆนๅผ
# ็จ $RANDOM ็ๆ็ฎๅ้ๆบๆฐ๏ผ่ๅด 0-32767๏ผ
echo $RANDOM
echo $(( RANDOM % 100 )) # 0-99 ็้ๆบๆฐ
# === /dev/tcp โ bash ๅ
็ฝฎ TCP ๅฎขๆท็ซฏ ===
# bash ็นๆๅ่ฝ๏ผ้่ฎพๅคๆไปถ๏ผ็ฑ bash ๅ
้จๅค็๏ผ
# ๆ ผๅผ๏ผ/dev/tcp/host/port
# ๆต่ฏ็ซฏๅฃๆฏๅฆๅผๆพ๏ผๆฏ nc ๆดไพฟๆบ๏ผไธ้่ฆๅฎ่ฃ
้ขๅคๅทฅๅ
ท๏ผ
if (: /dev/null; then
echo "Port 80 is open"
else
echo "Port 80 is closed"
fi
# ๅ้็ฎๅ HTTP ่ฏทๆฑ
exec 3<>/dev/tcp/example.com/80
echo -e "GET / HTTP/1.0\r\nHost: example.com\r\n\r\n" >&3
cat &-
# ๆฃๆฅๆๅกๆฏๅฆๅญๆดป๏ผ็จไบ็ๆง่ๆฌ๏ผ
check_port() {
local host=$1 port=$2
(: /dev/null
}
check_port localhost 3306 && echo "MySQL is up" || echo "MySQL is down"
11.11 Subshells and Pipeline Environment Isolation
Understanding when subshells are created and how they isolate from the parent shell is essential for debugging "mysteriously disappearing" variables in shell scripts. Here are the main scenarios that spawn a subshell:
# === ๆพๅผๅญ shell๏ผๅๆฌๅท () ===
# ๅญ shell ็ปงๆฟ็ถ shell ็ๅ้๏ผไฝไฟฎๆนไธๅฝฑๅ็ถ shell
x=10
(
echo "In subshell: x=$x" # 10๏ผ็ปงๆฟ๏ผ
x=999
echo "Modified in subshell: x=$x" # 999
)
echo "In parent: x=$x" # 10๏ผไธๅๅฝฑๅ๏ผ
# ๅญ shell ็ BASH_SUBSHELL ๅ้
echo "Parent: $BASH_SUBSHELL" # 0
(echo "Child: $BASH_SUBSHELL") # 1
((echo "Grandchild: $BASH_SUBSHELL")) # ๆณจๆ๏ผ่ฟๆฏ็ฎๆฏ่กจ่พพๅผ๏ผไธๆฏๅญ shell๏ผ
( ( echo "Grandchild: $BASH_SUBSHELL" ) ) # 2๏ผๆญฃ็กฎๅตๅฅๅญ shell๏ผ
# === ่ฑๆฌๅท็ปๅๅฝไปค {} โ ๅฝๅ Shell ๆง่ก ===
# ๅ้ไฟฎๆนๅฏน็ถ shell ๅฏ่ง๏ผ
y=10
{
y=999
echo "In group: y=$y" # 999
}
echo "After group: y=$y" # 999๏ผๅ้ไฟ็๏ผ๏ผ
# ๆณจๆ๏ผ{} ๅ
ๆๅไธๆกๅฝไปคๅ้่ฆๅๅท๏ผไธ { ๅ้่ฆ็ฉบๆ ผ
# === ็ฎก้ไธญ็ๅญ shell ===
# ็ฎก้็ๆฏไธช็ปไปถ๏ผ้ป่ฎค๏ผ้ฝๅจๅญ shell ไธญ
VAR=""
echo "hello" | VAR=$(cat); echo "VAR=$VAR" # VAR=๏ผ็ฉบ๏ผๅญ shell๏ผ
# ๆญฃ็กฎๅๆณ๏ผ็จ่ฟ็จๆฟๆข้ฟๅ
ๅญ shell
VAR=$(echo "hello"); echo "VAR=$VAR" # VAR=hello
# === ๅฝไปคๆฟๆข $() ไนๆฏๅญ shell ===
result=$(
x=100
echo $((x * 2))
)
echo "result=$result" # result=200
echo "x=$x" # x=๏ผ็ฉบ๏ผx ๅจๅญ shell ไธญๅฎไน๏ผ
# === ๅๅฐๅฝไปค & ไนๆฏๅญ shell ===
bg_var=""
(bg_var="set in background") &
wait
echo "bg_var=$bg_var" # ็ฉบ
Chapter Summary: This chapter traced the complete working chain of file descriptors, redirection, and pipes from the kernel level. Key points: redirection is FD table modification between fork and exec; pipes are 64 KB kernel buffers; the order of
2>&1matters critically; pipe right-side commands run in subshells (variables don't propagate);exec N>filepersistently manipulates the current shell's FDs; named pipes enable cross-process communication. With these mechanisms mastered, the next chapter's script engineering will come naturally.
Previous
โ Ch10: Functions
Next
Ch12: Engineering โ