萬盛學電腦網

您现在的位置: 萬盛學電腦網 >> 作業系統 >  >> Linux 教學

Bash 重定向完全指南:標準流、文件描述符、Here Document 與實戰技巧

重定向基礎概念

  • 文件描述符 (FD):進程打開文件的索引,0=stdin,1=stdout,2=stderr
  • Shell 負責重定向設置:在命令執行前完成 FD 綁定,命令本身無感知
  • 執行順序:重定向從左到右處理,後者可覆蓋前者

基礎重定向語法

語法說明等效長形式
cmd > filestdout 覆蓋寫入文件cmd 1> file
cmd >> filestdout 追加寫入文件cmd 1>> file
cmd < filestdin 從文件讀取cmd 0< file
cmd 2> filestderr 覆蓋寫入文件-
cmd 2>> filestderr 追加寫入文件-
cmd &> filestdout+stderr 覆蓋寫入cmd > file 2>&1
cmd &>> filestdout+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 仍去終端
copyright © 萬盛學電腦網 all rights reserved