Chapter 11

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:

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>&1 and command 2>&1 > file are 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>&1 matters critically; pipe right-side commands run in subshells (variables don't propagate); exec N>file persistently 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 โ†’
Rate this chapter
4.8  / 5  (27 ratings)

๐Ÿ’ฌ Comments