重定向基礎概念
- 文件描述符 (FD):進程打開文件的索引,0=stdin,1=stdout,2=stderr
- Shell 負責重定向設置:在命令執行前完成 FD 綁定,命令本身無感知
- 執行順序:重定向從左到右處理,後者可覆蓋前者
基礎重定向語法
| 語法 | 說明 | 等效長形式 |
cmd > file | stdout 覆蓋寫入文件 | cmd 1> file |
cmd >> file | stdout 追加寫入文件 | cmd 1>> file |
cmd < file | stdin 從文件讀取 | cmd 0< file |
cmd 2> file | stderr 覆蓋寫入文件 | - |
cmd 2>> file | stderr 追加寫入文件 | - |
cmd &> file | stdout+stderr 覆蓋寫入 | cmd > file 2>&1 |
cmd &>> file | stdout+stderr 追加寫入 | cmd >> file 2>&1 |
cmd >| file | 強制覆蓋 (忽略 noclobber) | - |
文件描述符操作
複製文件描述符
# 複製 FD 1 到 FD 3 (備份 stdout)
exec 3>&1
# 複製 FD 0 到 FD 4 (備份 stdin)
exec 4<&0
# 關閉 FD
exec 3>&-
exec 4<&-
交換 stdout/stderr
# 經典交換技巧:3>&1 1>&2 2>&3
cmd 3>&1 1>&2 2>&3
# 1. 3>&1 : FD3 指向原 stdout
# 2. 1>&2 : FD1 指向原 stderr
# 3. 2>&3 : FD2 指向 FD3 (原 stdout)
# 結果: stdout 和 stderr 交換
移動文件描述符
# 將 FD 5 移動到 FD 1 (FD 5 關閉)
exec 1>&5-
# 實際應用較少,理解即可
Here Document (<<) 與 Here String (<<<)
Here Document
# 基本用法
cat << 'EOF'
這裡的內容不會變量展開、命令替換、轉義
$HOME 不會展開
`date` 不會執行
EOF
# 允許展開 (無引號)
cat << EOF
當前用戶: $HOME
時間: $(date)
EOF
# 移除行首 Tab (<<-)
cat <<- 'EOF'
縮進的行首 Tab 會被移除
方便腳本內縮進對齊
EOF
Here String
# 將字符串作為 stdin 傳給命令
cat <<< "hello world"
read var <<< "single line input"
實戰核心模式
模式 1:雙輸出 (屏幕顯示 + 文件記錄)
# tee 命令
cmd 2>&1 | tee log.txt
cmd 2>&1 | tee -a log.txt # 追加
# 不用 tee (Bash 4+)
cmd > >(tee log.txt) 2> >(tee err.log >&2)
模式 2:分離 stdout/stderr 到不同文件
cmd > stdout.log 2> stderr.log
模式 3:丟棄輸出
# 只看錯誤
cmd > /dev/null
# 只看輸出
cmd 2> /dev/null
# 完全靜默
cmd > /dev/null 2>&1
cmd &> /dev/null
模式 4:管道中 stderr 傳遞
# 默認管道只傳 stdout
cmd1 | cmd2
# 傳遞 stderr (Bash 4+)
cmd1 2>&1 | cmd2
# 或
{ cmd1 2>&1; } | cmd2
模式 5:執行時動態重定向 (exec)
#!/bin/bash
# 整個腳本輸出重定向
exec > >(tee -a /var/log/myscript.log) 2>&1
echo "這行同時輸出到屏幕和日誌"
ls /nonexistent # 錯誤也記錄
模式 6:進程替換 (Process Substitution)
# 同時輸出到多個命令
tee >(gzip > access.log.gz) >(grep error > errors.log) < access.log
# diff 比較命令輸出
diff <(cmd1) <(cmd2)
diff <(ls dir1) <(ls dir2)
經典陷阱與順序敏感性
陷阱 1:重定向順序決定綁定對象
# 正確:先綁定 stdout 到文件,再將 stderr 指向 stdout (即文件)
ls notexist > out.txt 2>&1
# out.txt 同時包含 stdout 和 stderr
# 錯誤:先將 stderr 指向當前 stdout (終端),再將 stdout 重定向到文件
ls notexist 2>&1 > out.txt
# stderr 仍輸出到終端,stdout 進文件
陷阱 2:管道中 set -o pipefail
# 默認管道返回最後命令狀態
false | true; echo $? # 0
# 啟用 pipefail
set -o pipefail
false | true; echo $? # 1
陷阱 3:變量賦值與重定向順序
# 變量賦值在重定向之前處理
var=value cmd > file # var 僅對 cmd 生效,不影響當前 Shell
# 等價於
var=value; cmd > file; unset var
高級應用:日誌多路複用
#!/bin/bash
# multi_log.sh - 同時輸出到控制台、文件、Syslog、遠程
exec 3>&1 # 備份 stdout
exec 4>&2 # 備份 stderr
# 多路輸出函數
multi_tee() {
local level="$1" # info/warn/error
shift
local msg="$*"
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
local formatted="[$timestamp] [$level] $msg"
# 控制台 (帶顏色)
case $level in
error) echo -e "\033[31m$formatted\033[0m" >&3 ;;
warn) echo -e "\033[33m$formatted\033[0m" >&3 ;;
*) echo "$formatted" >&3 ;;
esac
# 本地文件
echo "$formatted" >> /var/log/myapp.log
# Syslog
logger -t myapp -p "user.$level" "$msg"
# 遠程 (可選)
# echo "$formatted" | nc -w1 logserver 514
}
# 使用
multi_echo "服務啟動"
multi_warn "配置文件缺失,使用默認值"
multi_error "數據庫連接失敗"
總結速查卡
# 基礎
>file # 1>file stdout 覆蓋
>>file # 1>>file stdout 追加
file # stderr 覆蓋
2>>file # stderr 追加
&>file # stdout+stderr 覆蓋
&>>file # stdout+stderr 追加
# FD 操作
N>&M # FD N 指向 FD M 所指文件
N<&M # FD N 讀取 FD M 所指文件
N>&- # 關閉 FD N
N<&- # 關閉 FD N
# Here Doc
<<'EOF' # 字面量
<file 2>&1 # 正確合併
cmd 2>&1 >file # 錯誤:stderr 仍去終端