开发者

Bash重定向完全指南(超详细!)

开发者 https://www.devze.com 2026-01-05 09:57 出处:网络 作者: cxxu1375
价值2999元 Java视频教程限时免费下载
专为Java开发者设计,涵盖核心技术、架构设计、性能优化等
立即下载
目录一、重定向概述1.1 文件描述符基础1.2 重定向的基本原理1.3 重定向操作符的位置1.4 重定向的处理顺序二、输入重定向(Input Redirection)2.1 基本语法2.2 判断是shell还是外部程序打开文件2.3 实际应用2.4 指定
目录
  • 一、重定向概述
    • 1.1 文件描述符基础
    • 1.2 重定向的基本原理
    • 1.3 重定向操作符的位置
    • 1.4 重定向的处理顺序
  • 二、输入重定向(Input Redirection)
    • 2.1 基本语法
    • 2.2 判断是shell还是外部程序打开文件
    • 2.3 实际应用
    • 2.4 指定文件描述符
  • 三、输出重定向(Output Redirection)
    • 3.1 基本语法
    • 3.2 基本行为
    • 3.3 noclobber 选项和>|
    • 3.4 重定向错误输出
  • 四、追加重定向(Appending Redirected Output)
    • 4.1 基本语法
    • 4.2 使用示例
  • 五、同时重定向标准输出和标准错误(输出聚合)
    • 5.1 两种语法形式(&>,>&)
    • 5.2 使用示例
    • 5.3 追加形式&>>
    • 5.4 注意事项
  • 六、Here Documents
    • 6.1 基本语法
    • 6.2 基本用法
    • 6.3 变量展开行为
    • 6.4 <<- 去除前导制表符
    • 6.5 实际应用
  • 七、Here Strings
    • 7.1 基本语法
    • 7.2 使用示例
    • 7.3 Here String vs echo + 管道
  • 八、复制文件描述符
    • 8.1 复制输入文件描述符
    • 8.2 复制输出文件描述符
    • 8.3 关闭文件描述符
    • 8.4 实际应用示例
  • 九、移动文件描述符
    • 9.1 移动输入文件描述符
    • 9.2 移动输出文件描述符
    • 9.3 复制 vs 移动 对比
  • 十、读写文件描述符
    • 10.1 基本语法
    • 10.2 使用示例
    • 10.3 实际应用
  • 十一、{varname} 语法详解
    • 11.1 自动分配文件描述符
    • 11.2 无需 exec 的持久文件描述符
    • 11.3 与 exec 方式的对比
    • 11.4 varredir_close 选项
    • 11.5 实际应用示例
  • 十二、特殊文件名处理
    • 12.1 /dev/fd/n
    • 12.2 /dev/stdin, /dev/stdout, /dev/stderr
    • 12.3 /dev/tcp 和 /dev/udp
  • 十三、重定向中的展开
    • 13.1 支持的展开类型
      • 13.2 多单词错误
  • 十四、文件描述符使用注意事项
    • 14.1 避免与 shell 内部 fd 冲突
      • 14.2 fd 泄漏
        • 14.3 子进程继承
        • 十五、综合实战示例
          • 15.1 日志系统
            • 15.2 进度和输出分离
              • 15.3 安全的临时文件处理
                • 15.4 同时捕获 stdout 和 stderr
                • 十六、常见问题与陷阱
                  • 16.1 在管道中的变量作用域
                    • 16.2 重定向 vs 管道
                      • 16.3 /dev/null 的正确使用
                      • 总结

                        一、重定向概述

                        重定向是 Shell 编程中最核心的概念之一。它允许你改变命令的输入来源和输出目的地,而不是使用默认的键盘输入和屏幕输出。

                        1.1 文件描述符基础

                        在 Unix/linux 系统中,每个进程启动时都会自动打开三个标准文件描述符:

                        文件描述符名称默认设备用途
                        0stdin(标准输入)键盘程序读取输入
                        1stdout(标准输出)终端屏幕程序正常输出
                        2stderr(标准错误)终端屏幕程序错误信息
                        # 查看当前 shell 打开的文件描述符
                        ls -la /proc/$$/fd
                        # 输出示例:
                        # lrwx------ 1 user user 64 Jan  3 10:00 0 -> /dev/pts/0
                        # lrwx------ 1 user user 64 Jan  3 10:00 1 -> /dev/pts/0
                        # lrwx------ 1 user user 64 Jan  3 10:00 2 -> /dev/pts/0
                        
                        # cxxu@CxxuDesk 17:14:09> <~>
                        $ ls -la /proc/$$/fd
                        total 0
                        dr-x------ 2 cxxu cxxu  6 Jan  3 16:57 .
                        dr-xr-xr-x 9 cxxu cxxu  0 Jan  3 16:57 ..
                        lrwx------ 1 cxxu cphpxxu 64 Jan  3 16:57 0 -> /dev/pts/0
                        lrwx------ 1 cxxu cxxu 64 Jan  3 16:57 1 -> /dev/pts/0
                        l-wx------ 1 cxxu cxxu 64 Jan  3 16:57 10 -> /home/cxxu/output.txt
                        lrwx------ 1 cxxu cxxu 64 Jan  3 16:57 2 -> /dev/pts/0
                        lrwx------ 1 cxxu cxxu 64 Jan  3 16:57 255 -> /dev/pts/0
                        lrwx------ 1 cxxu cxxu 64 Jan  3 16:57 5 -> /dev/ptmx
                        

                        其中$$表示当前shell进程id

                        1.2 重定向的基本原理

                        重定向操作符会在命令执行之前Shell 解释和处理。这意味着:

                        # 即使命令不存在,文件也会被创建(因为重定向先于命令执行)
                        nonexistent_command > output.txt
                        ls -la output.txt
                        # -rw-r--r-- 1 user user 0 Jan  3 10:00 output.txt  # 文件已创建,但为空
                        

                        此外,shell是从左往右出来重定向的,这在包含多个重定向用法的命令行要注意.

                        1.3 重定向操作符的位置

                        重定向可以出现在命令的任何位置:

                        # 以下三种写法完全等价
                        echo hello > file.txt
                        > file.txt echo hello
                        echo > file.txt hello
                        

                        shell优先识别(处理)命令行中的重定向部分>file.txt,剩下的是命令部分(非重定向部分),即echo hello

                        然后文件file.txt被创建(如果没有的话),然后echo命令的输出被重定向到file.txt

                        1.4 重定向的处理顺序

                        重定向按照从左到右的顺序处理,这一点极其重要:

                        # 示例 1:stdout 和 stderr 都重定向到 dirlist
                        ls > dirlist 2>&1
                        # 执行顺序:
                        # 1. > dirlist     : fd 1 指向 dirlist 文件
                        # 2. 2>&1          : fd 2 复制 fd 1(此时 fd 1 已指向 dirlist)
                        # 结果:fd 1 和 fd 2 都指向 dirlist
                        
                        # 示例 2:只有 stdout 重定向到 dirlist
                        ls 2>&1 > dirlist
                        # 执行顺序:
                        # 1. 2>&1          : fd 2 复制 fd 1(此时 fd 1 还指向终端)
                        # 2. > dirlist     : fd 1 指向 dirlist 文件
                        # 结果:fd 2 指向终端,fd 1 指向 dirlist
                        

                        二、输入重定向(Input Redirection)

                        2.1 基本语法

                        [n]<word
                        
                        1. 如果省略 n默认为文件描述符 0(标准输入)
                        2. 并且n不一定是整数(虽然常见的情况是整数),还可以是单词{varname},这是高级用法.

                        2.2 判断是shell还是外部程序打开文件

                        # 从文件读取输入(shell打开文件,cat接受输入)
                        cat < input.txt
                        
                        # 等价于下面(但语义不同:一个是 shell 打开文件,一个是 cat 打开文件)
                        # cat直接打开文件
                        cat input.txt
                        
                        # 区别演示,打开不存在的文件时,上述两种写法报错者不同(分别由shell程序和cat程序抛出)
                        cat < nonexistent.txt  # shell 报错:bash: nonexistent.txt: No such file or directory
                        cat nonexistent.txt    # cat 报错:cat: nonexistent.txt: No such file or directory
                        

                        2.3 实际应用

                        # 使用 while 循环逐行读取文件
                        while read -r line; do
                            echo "Line: $line"
                        done < data.txt
                        
                        # 从文件读取数据进行排序(shell从左往右解释重定向,首先将sort命令的输入重定向为unsorted.txt,然后将sort处理结果重定向到sorted.txt)
                        sort < unsorted.txt > sorted.txt
                        
                        # 用于交互式程序的自动化
                        mysql -u root -p < setup.sql
                        
                        # 多个输入重定向(后者覆盖前者)
                        cat < file1.txt < file2.txt  # 只会读取 file2.txt
                        

                        2.4 指定文件描述符

                        # 将文件描述符 3 关联到输入文件
                        exec 3< input.txt
                        read line <&3      # 从 fd 3 读取一行(注意指针偏移,下一次读取自动读取原文中的第2行,依次类推)
                        echo "$line"
                        exec 3<&-          # 关闭 fd 3
                        

                        具体案例:

                        # cxxu@CxxuDesk 17:android46:08> <~>
                        $ exec 3<config.txt
                        
                        # cxxu@CxxuDesk 17:46:25> <~>
                        $ read l1 <&3
                        
                        # cxxu@CxxuDesk 17:46:48> <~>
                        $ echo "$l1"
                        line1
                        
                        # cxxu@CxxuDesk 17:46:57> <~>
                        $ read l2 <&3
                        
                        # cxxu@CxxuDesk 17:47:07> <~>
                        $ echo "$l2"
                        line2
                        

                        检查指定文件描述符是否被关闭

                        # cxxu@CxxuDesk 17:48:14> <~>
                        $ ls -la /proc/$$/fd
                        total 0
                        dr-x------ 2 cxxu cxxu  8 Jan  3 16:57 .
                        dr-xr-xr-x 9 cxxu cxxu  0 Jan  3 16:57 ..
                        lrwx------ 1 cxxu cxxu 64 Jan  3 16:57 0 -> /dev/pts/0
                        lrwx------ 1 cxxu cxxu 64 Jan  3 16:57 1 -> /dev/pts/0
                        l-wx------ 1 cxxu cxxu 64 Jan  3 16:57 10 -> /home/cxxu/output.txt
                        l-wx------ 1 cxxu cxxu 64 Jan  3 17:34 11 -> /home/cxxu/output.txt
                        lrwx------ 1 cxxu cxxu 64 Jan  3 16:57 2 -> /dev/pts/0
                        lrwx------ 1 cxxu cxxu 64 Jan  3 16:57 255 -> /dev/pts/0
                        lr-x------ 1 cxxu cxxu 64 Jan  3 17:46 3 -> /home/cxxu/config.txt
                        lrwx------ 1 cxxu cxxu 64 Jan  3 16:57 5 -> /dev/ptmx
                        
                        # cxxu@CxxuDesk 17:48:30> <~>
                        $ exec 3<&-
                        
                        # cxxu@CxxuDesk 17:48:43> <~>
                        $ ls -la /proc/$$/fd
                        total 0
                        dr-x------ 2 cxxu cxxu  7 Jan  3 16:57 .
                        dr-xr-xr-x 9 cxxu cxxu  0 Jan  3 16:57 ..
                        lrwx------ 1 cxxu cxxu 64 Jan  3 16:57 0 -> /dev/pts/0
                        lrwx------ 1 cxxu cxxu 64 Jan  3 16:57 1 -> /dev/pts/0
                        l-wx------ 1 cxxu cxxu 64 Jan  3 16:57 10 -> /home/cxxu/output.txt
                        l-wx------ 1 cxxu cxxu 64 Jan  3 17:34 11 -> /home/cxxu/output.txt
                        lrwx------ 1 cxxu cxxu 64 Jan  3 16:57 2 -> /dev/pts/0
                        lrwx------ 1 cxxu cxxu 64 Jan  3 16:57 255 -> /dev/pts/0
                        lrwx------ 1 cxxu cxxu 64 Jan  3 16:57 5 -> /dev/ptmx
                        

                        三、输出重定向(Output Redirection)

                        3.1 基本语法

                        [n]>[|]word
                        

                        如果省略 n,默认为文件描述符 1(标准输出)。

                        其中|启用的时候(变成>|)会强制重定向,即便shell选项设置为默认不覆盖(noclobber)

                        3.2 基本行为

                        # 创建新文件或覆盖现有文件
                        echo "Hello" > output.txt
                        
                        # 如果文件存在,内容被清空后写入
                        echo "World" > output.txt
                        cat output.txt
                        # World  (注意:Hello 已被覆盖)
                        

                        3.3 noclobber 选项和>|

                        noclobber 选项可以防止意外覆盖文件:

                        # 启用 noclobber
                        set -o noclobber
                        # 或者
                        set -C
                        
                        # 尝试覆盖现有文件会失败
                        echo "test" > existing_file.txt
                        # bash: existing_file.txt: cannot overwrite existing file
                        
                        # 使用 >| 强制覆盖
                        echo "test" >| existing_file.txt  # 成功
                        
                        # 禁用 noclobber
                        set +o noclobber
                        # 或者
                        set +C
                        

                        3.4 重定向错误输出

                        # 只重定向标准错误
                        ls nonexistent 2> error.log
                        
                        # 标准输出和标准错误分别重定向
                        command > stdout.log 2> stderr.log
                        
                        # 丢弃错误信息
                        command 2> /dev/null
                        
                        # 丢弃所有输出
                        command > /dev/null 2>&1
                        

                        四、追加重定向(Appending Redirected Output)

                        4.1 基本语法

                        [n]>>word
                        

                        4.2 使用示例

                        # 追加内容到文件
                        echo "First line" > log.txt
                        echo "Second line" >> log.txt
                        echo "Third line" >> log.txt
                        cat log.txt
                        # First line
                        # Second line
                        # Third line
                        
                        # 追加错误输出
                        command 2>> error.log
                        
                        
                        
                        # 实际应用:日志记录(演示目录/var/log/可能会遇到权限问题)
                        log() {
                            echo "$(date '+%Y-%m-%d %H:%M:%S') - $*" >> ~/myapp.log
                        }
                        log "Application started"
                        log "Processing data..."
                        

                        五、同时重定向标准输出和标准错误(输出聚合)

                        5.1 两种语法形式(&>,>&)

                        # 形式 1(推荐)
                        &>word
                        
                        # 形式 2
                        >&word
                        

                        当使用第二种形式时,word 不可能扩展为一个数字或‘ - ’

                        两者在语义上等同于:

                        >word 2>&1
                        

                        5.2 使用示例

                        # 所有输出都写入文件
                        command &> all_output.txt
                        
                        # 丢弃所有输出
                        command &> /dev/null
                        
                        # 等价写法对比
                        ls /exists /nonexistent &> output.txt
                        ls /exists /nonexistent > output.txt 2>&1  # 等价
                        

                        5.3 追加形式&>>

                        # 追加所有输出
                        command &>> all_output.txt
                        
                        # 等价于
                        command >> all_output.txt 2>&1
                        

                        相比于> word 2>&1只在开头更改为>>

                        5.4 注意事项

                        当使用 >&word 这个形式时,word 不能是数字- 因为那会被解释为文件描述符操作

                        # 这是复制文件描述符,而不是将输出聚合到名为3的文件
                        command >&3            # fd 1 复制到 fd 3,相当于1>&3
                        
                        # 安全起见,推荐使用 &> 形式
                        command &>output.txt   # 清晰明确
                        # 
                        ls /usr/bin/env 'abab' &> 3
                        
                        # cxxu@CxxuDesk 18:12:30> <~>
                        $ ls /usr/bin/env 'abab' &> 3
                        
                        # cxxu@CxxuDesk 18:15:07> <~>
                        $ cat 3
                        ls: cannot Access 'abab': No such file or directory
                        /usr/bin/env
                        

                        六、Here Documents

                        6.1 基本语法

                        [n]<<[-]word
                            here-document
                        delimiter
                        

                        6.2 基本用法

                        # 基本的 here document
                        cat << EOF
                        This is line 1
                        This is line 2
                        This is line 3
                        EOF
                        
                        # 输出:
                        # This is line 1
                        # This is line 2
                        # This is line 3
                        

                        6.3 变量展开行为

                        name="World"
                        
                        # 不带引号的分隔符:变量会被展开
                        cat << EOF
                        Hello, $name!
                        Current directory: $(pwd)
                        Sum: $((1 + 2))
                        EOF
                        # 输出:
                        # Hello, World!
                        # Current directory: /home/user
                        # Sum: 3
                        
                        # 带引号的分隔符:禁止所有展开
                        cat << 'EOF'
                        Hello, $name!
                        Current directory: $(pwd)
                        Sum: $((1 + 2))
                        EOF
                        # 输出:
                        # Hello, $name!
                        # Current directory: $(pwd)
                        # Sum: $((1 + 2))
                        
                        # 部分引用也会禁止展开
                        cat << "EOF"
                        Hello, $name!
                        EOF
                        # 输出:
                        # Hello, $name!
                        
                        cat << E"O"F
                        Hello, $name!
                        EOF
                        # 输出:
                        # Hello, $name!
                        

                        6.4 <<- 去除前导制表符

                        # 使用 <<- 可以在脚本中保持良好的缩进
                        if true; then
                            cat <<- EOF
                        		This line has a tab prefix
                        		So does this one
                        		And this one too
                        	EOF
                        fi
                        # 输出(前导制表符被去除):
                        # This line has a tab prefix
                        # So does this one
                        # And this one too
                        
                        # 注意:只去除制表符(Tab),不去除空格
                        

                        6.5 实际应用

                        # 生成配置文件
                        cat << EOF > /etc/myapp.conf
                        # Configuration file for MyApp
                        # Generated on $(date)
                        
                        server_name = localhost
                        port = 8080
                        debug = false
                        EOF
                        
                        # SQL 脚本
                        mysql -u root -p << EOF
                        CREATE DATABASE IF NOT EXISTS mydb;
                        USE mydb;
                        CREATE TABLE IF NOT EXISTS users (
                            id INT PRIMARY KEY AUTO_INCREMENT,
                            name VARCHAR(100)
                        );
                        EOF
                        
                        # 多行 SSH 命令
                        ssh user@remote << 'EOF'
                        cd /var/log
                        tail -n 100 syslog
                        df -h
                        EOF
                        
                        # 函数中使用
                        generate_html() {
                            cat << EOF
                        <!DOCTYPE html>
                        <html>
                        <head><title>$1</title></head>
                        <body>
                        <h1>$1</h1>
                        <p>$2</p>
                        </body>
                        </html>
                        EOF
                        }
                        generate_html "Welcome" "Hello, World!" > index.html
                        

                        七、Here Strings

                        7.1 基本语法

                        [n]<<< word
                        

                        7.2 使用示例

                        # 基本用法
                        cat <<< "Hello, World!"
                        # Hello, World!
                        
                        # 与 here document 对比
                        # Here document(多行)
                        cat << EOF
                        Hello
                        EOF
                        
                        # Here string(单行,更简洁)
                        cat <<< "Hello"
                        
                        # 变量展开
                        name="Alice"
                        cat <<< "Hello, $name!"
                        # Hello, Alice!
                        
                        # 命令替换
                        cat <<< "Today is $(date +%A)"
                        # Today is Friday
                        
                        # 用于需要 stdin 输入的命令
                        read var <<< "input value"
                        echo "$var"
                        # input value
                        
                        # 实际应用:处理字符串
                        # 计算字符串中的单词数
                        wc -w <<< "one two three four"
                        # 4
                        
                        # 字符串分割
                        IFS=: read user pass uid gid gecos home shell <<< "root:x:0:0:root:/root:/bin/bash"
                        echo "User: $user, Home: $home, Shell: $shell"
                        # User: root, Home: /root, Shell: /bin/bash
                        
                        # bc 计算器
                        bc <<< "scale=2; 10/3"
                        # 3.33
                        

                        7.3 Here String vs echo + 管道

                        # 使用 here string(更高效,不创建子进程)
                        cat <<< "Hello"
                        
                        # 使用 echo + 管道(创建子进程)
                        echo "Hello" | cat
                        
                        # 性能差异示例
                        time for i in {1..10000}; do cat <<< "test" > /dev/null; done
                        time for i in {1..10000}; do echo "test" | cat > /dev/null; done
                        # here string 通常更快
                        

                        八、复制文件描述符

                        8.1 复制输入文件描述符

                        [n]<&word
                        # 将 fd 3 设为 fd 0 的副本
                        exec 3<&0
                        
                        # 实际应用:保存和恢复 stdin
                        exec 3<&0           # 保存原始 stdin 到 fd 3
                        exec 0< input.txt   # 重定向 stdin 到文件
                        # ... 一些操作 ...
                        exec 0<&3           # 从 fd 3 恢复 stdin
                        exec 3<&-           # 关闭 fd 3
                        

                        8.2 复制输出文件描述符

                        [n]>&word
                        # 将 fd 3 设为 fd 1 的副本
                        exec 3>&1
                        
                        # 经典模式:交换 stdout 和 stderr
                        exec 3>&1 1>&2 2>&3 3>&-
                        # 执行后:原来的 stdout 变成 stderr,原来的 stderr 变成 stdout
                        

                        8.3 关闭文件描述符

                        # 关闭输入文件描述符
                        exec 3<&-
                        
                        # 关闭输出文件描述符
                        exec 3>&-
                        
                        # 关闭标准输入
                        exec 0<&-
                        
                        # 关闭标准输出
                        exec 1>&-
                        

                        8.4 实际应用示例

                        # 示例:同时捕获 stdout 和 stderr 到不同变量
                        {
                            output=$(command 2>&1 1>&3)
                            exit_code=$?
                        } 3>&1
                        error=$output
                        # 此时 $output 包含 stderr,stdout 正常显示
                        
                        # 更完整的版本
                        capture_output() {
                            local stdout stderr exit_code
                            exec 3>&1 4>&2
                            stdout=$( { stderr=$( "$@" 2>&1 1>&3 3>&- ); exit_code=$?; } 2>&1 )
                            exec 3>&- 4>&-
                            echo "stdout: $stdout"
                            echo "stderr: $stderr"
                            echo "exit: $exit_code"
                        }
                        

                        九、移动文件描述符

                        9.1 移动输入文件描述符

                        [n]<&digit-
                        

                        移动 = 复制 + 关闭原描述符

                        # 将 fd 3 移动到 fd 0
                        exec 0<&3-
                        # 等价于:
                        # exec 0<&3
                        # exec 3<&-
                        

                        9.2 移动输出文件描述符

                        [n]>&digit-
                        # 将 fd 3 移动到 fd 1
                        exec 1>&3-
                        
                        # 实际应用:日志重定向后恢复
                        exec 3>&1                    # 保存 stdout
                        exec 1> logfile.txt          # stdout 重定向到文件
                        echo "This goes to log"
                        exec 1>&3-                   # 恢复 stdout 并关闭 fd 3(一步完成)
                        echo "This goes to terminal"
                        

                        9.3 复制 vs 移动 对比

                        # 复制:原文件描述符保持打开
                        exec 3>&1      # fd 3 是 fd 1 的副本,fd 1 仍然有效
                        
                        # 移动:原文件描述符被关闭
                        exec 3>&1-     # fd 3 是 fd 1 的副本,fd 1 被关闭
                        

                        十、读写文件描述符

                        10.1 基本语法

                        [n]<>word
                        

                        以读写模式打开文件,如果文件不存在则创建。

                        10.2 使用示例

                        # 以读写模式打开文件
                        exec 3<> data.txt
                        
                        # 读取内容
                        read line <&3
                        echo "Read: $line"
                        
                        # 写入内容(注意:会覆盖当前位置的内容)
                        echo "New content" >&3
                        
                        # 关闭
                        exec 3>&-
                        

                        10.3 实际应用

                        # 简单的文件锁实现
                        lockfile="/tmp/mylock"
                        exec 200<>$lockfile
                        flock -n 200 || { echo "Another instance running"; exit 1; }
                        # ... 执行需要锁保护的操作 ...
                        
                        # 修改文件的特定部分(需要配合 seek,通常用其他工具更方便)
                        # Bash 本身不支持 seek,这种用法有限
                        

                        十一、{varname} 语法详解

                        11.1 自动分配文件描述符

                        Bash 4.1+ 支持使用 {varname} 让 shell 自动分配一个 ≥10 的文件描述符:

                        # 传统方式:手动指定 fd 编号
                        exec 3> output.txt
                        
                        # 新方式:自动分配
                        exec {myfd}> output.txt
                        echo "Allocated fd: $myfd"    # 输出类似:Allocated fd: 10
                        
                        # 使用分配的 fd
                        echo "Hello" >&$myfd
                        
                        # 关闭
                        exec {myfd}>&-
                        

                        11.2 无需 exec 的持久文件描述符

                        这是 {varname} 最强大的特性:

                        # 在普通命令中打开 fd,且 fd 会持续存在
                        echo "First line" {fd}> output.txt
                        
                        # fd 在命令结束后仍然有效
                        echo "Second line" >&$fd
                        echo "Third line" >&$fd
                        
                        cat output.txt
                        # First line
                        # Second line
                        # Third line
                        
                        # 手动关闭
                        exec {fd}>&-
                        

                        11.3 与 exec 方式的对比

                        # 方式 1:exec + 固定编号
                        exec 3> file.txt
                        echo "data" >&3
                        exec 3>&-
                        # 缺点:可能与其他代码冲突
                        
                        # 方式 2:exec + {varname}
                        exec {fd}> file.txt
                        echo "data" >&$fd
                        exec {fd}>&-
                        # 优点:自动分配,不冲突
                        # 缺点:仍需 exec
                        
                        # 方式 3:命令 + {varname}(最灵活)
                        : {fd}> file.txt     # : 是空命令
                        echo "data" >&$fd
                        exec {fd}>&-
                        # 优点:无需 exec,自动分配
                        

                        11.4 varredir_close 选项

                        # 查看当前设置
                        shopt varredir_close
                        
                        # 启用:当变量离开作用域时自动关闭 fd
                        shopt -s varredir_close
                        
                        # 禁用(默认)
                        shopt -u varredir_close
                        

                        11.5 实际应用示例

                        # 日志系统
                        init_logging() {
                            : {LOG_FD}>> /var/log/myapp.log
                        }
                        
                        log() {
                            echo "$(date): $*" >&$LOG_FD
                        }
                        
                        close_logging() {
                            exec {LOG_FD}>&-
                        }
                        
                        # 使用
                        init_logging
                        log "Application started"
                        log "Processing..."
                        close_logging
                        
                        # 多文件处理
                        process_files() {
                            : {input_fd}< input.txt
                            : {output_fd}> output.txt
                            : {error_fd}>> errors.log
                            
                            while read -u $input_fd line; do
                                if process "$line"; then
                                    echo "$line" >&$output_fd
                                else
                                    echo "Failed: $line" >&$error_fd
                                fi
                            done
                            
                            exec {input_fd}<&- {output_fd}>&- {error_fd}>&-
                        }
                        

                        十二、特殊文件名处理

                        12.1 /dev/fd/n

                        # 复制文件描述符
                        echo "Hello" > /dev/fd/1    # 等同于 echo "Hello"(写入 stdout)
                        
                        # 实际应用:让不支持 fd 的程序使用管道
                        diff <(sort file1) <(sort file2)
                        # 内部使用类似 /dev/fd/63 的机制
                        

                        12.2 /dev/stdin, /dev/stdout, /dev/stderr

                        # 明确指定标准流
                        cat /dev/stdin              # 从标准输入读取
                        echo "Hello" > /dev/stdout  # 写入标准输出
                        echo "Error" > /dev/stderr  # 写入标准错误
                        
                        # 在脚本中恢复标准流
                        some_function() {
                            # 即使 stdout 被重定向,仍可写入终端
                            echo "Debug info" > /dev/stderr
                        }
                        

                        12.3 /dev/tcp 和 /dev/udp

                        # TCP 连接
                        exec 3<>/dev/tcp/www.example.com/80
                        echo -e "GET / HTTP/1.1\r\nHost: www.example.com\r\nConnection: close\r\n\r\n" >&3
                        cat <&3
                        exec 3>&-
                        
                        # 检查端口是否开放
                        timeout 1 bash -c 'cat < /dev/tcp/localhost/22' && echo "SSH port open"
                        
                        # 简单的 HTTP 请求函数
                        http_get() {
                            local host=$1
                            local path=${2:-/}
                            exec 3<>/dev/tcp/$host/80
                            echo -e "GET $path HTTP/1.1\r\nHost: $host\r\nConnection: close\r\n\r\n" >&3
                            cat <&3
                            exec 3>&-
                        }
                        http_get "example.com" "/index.html"
                        
                        # UDP 示例(发送数据)
                        echo "test" > /dev/udp/localhost/514   # 发送到本地 syslog
                        
                        # 注意:这些是 Bash 特有功能,不是 POSIX 标准
                        # 某些系统可能需要编译时启用此功能
                        

                        十三、重定向中的展开

                        13.1 支持的展开类型

                        重定向操作符后面的 word 会经历以下展开:

                        # 1. 花括号展开(注意:通常不用于重定向)
                        # echo test > {a,b}.txt  # 这会导致错误,因为展开后有多个单词
                        
                        # 2. 波浪号展开
                        echo "test" > ~/output.txt           # 展开为 /home/user/output.txt
                        echo "test" > ~other_user/file.txt   # 展开为 /home/other_user/file.txt
                        
                        # 3. 参数和变量展开
                        logfile="/var/log/app.log"
                        echo "test" > "$logfile"
                        
                        # 4. 命令替换
                        echo "test" > "$(date +%Y%m%d).log"  # 例如:20260103.log
                        
                        # 5. 算术展开
                        n=1
                        echo "test" > "file$((n+1)).txt"     # file2.txt
                        
                        # 6. 引号去除
                        echo "test" > "output.txt"           # 引号被去除
                        
                        # 7. 文件名展开(通配符)
                        # 注意:如果展开后得到多个文件,会报错
                        echo "test" > *.txt                  # 如果匹配多个文件,报错
                        echo "test" > file?.txt              # 如果只匹配一个文件,OK
                        
                        # 8. 单词分割
                        filename="my file.txt"
                        echo "test" > $filename              # 错误!分割成两个单词
                        echo "test" > "$filename"            # 正确,保持为一个单词
                        

                        13.2 多单词错误

                        # 如果展开结果是多个单词,Bash 会报错
                        files="a.txt b.txt"
                        echo "test" > $files
                        # bash: $files: ambiguous redirect
                        
                        # 解决方案:确保只有一个单词
                        echo "test" > "$files"    # 写入名为 "a.txt b.txt" 的文件
                        # 或者使用循环
                        for f in $files; do echo "test" > "$f"; done
                        

                        十四、文件描述符使用注意事项

                        14.1 避免与 shell 内部 fd 冲突

                        # Shell 内部可能使用 fd 10 及以上
                        # 手动使用大编号 fd 时要小心
                        
                        # 不推荐
                        exec 10> myfile.txt    # 可能与 shell 内部冲突
                        
                        # 推荐:使用 {varname} 语法
                        exec {fd}> myfile.txt  # 让 shell 分配安全的 fd 编号
                        

                        14.2 fd 泄漏

                        # 错误:打开 fd 后忘记关闭
                        for i in {1..1000}; do
                            exec {fd}> "/tmp/file$i.txt"
                            echo "data" >&$fd
                            # 忘记关闭 fd!
                        done
                        # 可能导致 "Too many open files" 错误
                        
                        # 正确:总是关闭 fd
                        for i in {1..1000}; do
                            exec {fd}> "/tmp/file$i.txt"
                            echo "data" >&$fd
                            exec {fd}>&-  # 关闭
                        done
                        

                        14.3 子进程继承

                        # 文件描述符默认被子进程继承
                        exec 3> shared.txt
                        (
                            echo "From subshell" >&3  # 子 shell 可以使用 fd 3
                        )
                        
                        # 阻止继承(使用 close-on-exec 标志)
                        # Bash 本身不直接支持,需要其他手段
                        

                        十五、综合实战示例

                        15.1 日志系统

                        #!/bin/bash
                        
                        # 初始化日志
                        LOG_DIR="/var/log/myapp"
                        mkdir -p "$LOG_DIR"
                        
                        # 打开日志文件描述符
                        exec {LOG_INFO}>> "$LOG_DIR/info.log"
                        exec {LOG_ERROR}>> "$LOG_DIR/error.log"
                        exehttp://www.devze.comc {LOG_DEBUG}>> "$LOG_DIR/debug.log"
                        
                        # 日志函数
                        log_info()  { echo "$(date '+%F %T') [INFO]  $*" >&$LOG_INFO; }
                        log_error() { echo "$(date '+%F %T') [ERROR] $*" >&$LOG_ERROR; }
                        log_debug() { echo "$(date '+%F %T') [DEBUG] $*" >&$LOG_DEBUG; }
                        
                        # 清理函数
                        cleanup() {
                            exec {LOG_INFO}>&- {LOG_ERROR}>&- {LOG_DEBUG}>&-
                        }
                        trap cleanup EXIT
                        
                        # 使用
                        log_info "Application started"
                        log_debug "Initializing components..."
                        
                        if ! some_operation; then
                            log_error "Operation failed"
                        fi
                        
                        log_info "Application finished"
                        

                        15.2 进度和输出分离

                        #!/bin/bash
                        
                        # 保存原始 stdout 和 stderr
                        exec {ORIG_STDOUT}>&1
                        exec {ORIG_STDERR}>&2
                        
                        # 重定向所有输出到日志
                        exec 1>> process.log 2>&1
                        
                        # 进度信息写入原始终端
                        progress() {
                            echo "$*" >&$ORIG_STDOUT
                        }
                        
                        # 正常输出写入日志
                        echo "Starting process..."
                        
                        for i in {1..10}; do
                            echo "Processing step $i"      # 写入日志
                            progress "Progresjss: ${i}0%"    # 显示在终端
                            sleep 1
                        done
                        
                        echo "Process complete"
                        progress "Done!"
                        
                        # 恢复
                        exec 1>&$ORIG_STDOUT 2>&$ORIG_STDERR
                        exec {ORIG_STDOUT}>&- {ORIG_STDERR}>&-
                        

                        15.3 安全的临时文件处理

                        #!/bin/bash
                        
                        # 创建临时文件并打开 fd(文件可以立即www.devze.com删除,fd 仍然有效)
                        tmpfile=$(mktemp)
                        exec {tmp_fd}<>"$tmpfile"
                        rm "$tmpfile"  # 删除文件,但 fd 仍然可用
                        
                        # 写入临时数据
                        echo "Temporary data line 1" >&$tmp_fd
                        echo "Temporary data line 2" >&$tmp_fd
                        
                        # 回到文件开头读取
                        exec {tmp_fd}<&-
                        exec {tmp_fd}< /dev/fd/$tmp_fd  # 不能直接 seek,需要其他方式
                        
                        # 更实用的方式:使用进程替换
                        data=$(cat << 'EOF'
                        line 1
                        line 2
                        line 3
                        EOF
                        )
                        
                        while read line; do
                            echo "Processing: $line"
                        done <<< "$data"
                        

                        15.4 同时捕获 stdout 和 stderr

                        #!/bin/bash
                        
                        # 方法:使用临时文件和 fd
                        capture_both() {
                            local cmd="$*"
                            local stdout_file stderr_file
                            
                            stdout_file=$(mktemp)
                            stderr_file=$(mktemp)
                            
                            eval "$cmd" > "$stdout_file" 2> "$stderr_file"
                            local exit_code=$?
                            
                            CAPTURED_STDOUT=$(cat "$stdout_file")
                            CAPTURED_STDERR=$(cat "$stderr_file")
                            
                            rm "$stdout_file" "$stderr_file"
                            
                            return $exit_code
                        }
                        
                        # 使用
                        capture_both ls /exists /nonexistent
                        
                        echo "Exit code: $?"
                        echo "Stdout: $CAPTURED_STDOUT"
                        echo "Stderr: $CAPTURED_STDERR"
                        

                        十六、常见问题与陷阱

                        16.1 在管道中的变量作用域

                        # 问题:管道中的循环在子 shell 中运行
                        count=0
                        cat file.txt | while read line; do
                            ((count++))
                        done
                        echo "$count"  # 输出 0!变量修改在子 shell 中丢失
                        
                        # 解决方案 1:使用进程替换
                        count=0
                        while read line; do
                            ((count++))
                        done < <(cat file.txt)
                        echo "$count"  # 正确的计数
                        
                        # 解决方案 2:使用 here string
                        count=0
                        while read line; do
                            ((count++))
                        done <<< "$(cat file.txt)"
                        echo "$count"  # 正确的计数
                        
                        # 解决方案 3:使用 lastpipe 选项(Bash 4.2+)
                        shopt -s lastpipe
                        count=0
                        cat file.txt | while read line; do
                            ((count++))
                        done
                        echo "$count"  # 正确的计数
                        

                        16.2 重定向 vs 管道

                        # 重定向:直接连接文件和 fd
                        command < input.txt > output.txt
                        
                        # 管道:连接两个进程的 stdout 和 stdin
                        command1 | command2
                        
                        # 区别:
                        # - 重定向不创建额外进程
                        # - 管道两边各有一个进程
                        # - 重定向的文件需要存在(输入)或可创建(输出)
                        

                        16.3 /dev/null 的正确使用

                        # 丢弃 stdout
                        command > /dev/null
                        
                        # 丢弃 stderr
                        command 2> /dev/null
                        
                        # 丢弃所有输出
                        command > /dev/null 2>&1
                        command &> /dev/null  # 简写
                        
                        # 不要这样做(创建名为 /dev/null 的普通文件的风险)
                        command > /dev/null 2> /dev/null  # 两次打开,通常 OK,但不必要
                        

                        总结

                        Bash 重定向是一个功能强大的系统,核心要点包括:

                        1. 理解文件描述符 0(stdin)、1(stdout)、2(stderr)的概念
                        2. 重定向按从左到右的顺序处理
                        3. > 覆盖,>> 追加
                        4. 2>&1 将 stderr 重定向到 stdout 当前指向的位置
                        5. {varname} 语法提供了自动 fd 分配和持久化能力
                        6. Here documents 和 here strings 用于内联输入
                        7. 特殊文件 /dev/tcp/dev/udp 提供网络功能
                        8. 使用 exec 可以修改当前 shell 的 fd

                        到此这篇关于Bash重定向完全指南的文章就介绍到这了,更多相关Bash重定向内容请搜索编程客栈(www.devze.com)以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程客栈(www.devze.com)!

                        0
                        价值2999元 Java视频教程限时免费下载
                        专为Java开发者设计,涵盖核心技术、架构设计、性能优化等
                        立即下载

                        精彩评论

                        暂无评论...
                        验证码 换一张
                        取 消