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

Written by

in

文章目录
  • 重定向是 Shell 编程中最核心的概念之一。它允许你改变命令的输入来源和输出目的地,而不是使用默认的键盘输入和屏幕输出。
  • Bash 重定向是一个功能强大的系统,核心要点包括: 理解文件描述符 0(stdin)、1(stdout)、2(stderr)的概念 重定向按从左到右的顺序处理 > 覆盖,>> 追加 2>&1 将 stderr 重定向到 stdout 当前指向的位置 {varname} 语法提供了自动 fd 分配和持久化能力 Here documents 和 here strings 用于内联输入 特殊文件 /dev/tcp 和 /dev/udp 提供网络功能 使用 exec 可以修改当前 shell 的 fd 到此这篇关于Bash重定向完全指南的文章就介绍到这了,更多相关Bash重定向内容请搜索风君子博客以前的文章或继续浏览下面的相关文章希望大家以后多多支持风君子博客! 您可能感兴趣的文章: bash shell 重定向输入和输出的例子
  • 目录
    • 一、重定向概述
      • 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 编程中最核心的概念之一。它允许你改变命令的输入来源和输出目的地,而不是使用默认的键盘输入和屏幕输出。

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

                          文件描述符 名称 默认设备 用途
                          0 stdin(标准输入) 键盘 程序读取输入
                          1 stdout(标准输出) 终端屏幕 程序正常输出
                          2 stderr(标准错误) 终端屏幕 程序错误信息
                          # 查看当前 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 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
                          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

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

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

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

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

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

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

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

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

                          # 示例 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
                          

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

                          # 从文件读取输入(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
                          

                          # 使用 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
                          

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

                          具体案例:

                          # cxxu@CxxuDesk 17:46: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
                          

                          [n]>[|]word
                          

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

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

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

                          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
                          

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

                          [n]>>word
                          

                          # 追加内容到文件
                          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..."
                          

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

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

                          两者在语义上等同于:

                          >word 2>&1
                          

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

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

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

                          当使用 >&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
                          

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

                          # 基本的 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
                          

                          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!
                          

                          # 使用 <<- 可以在脚本中保持良好的缩进
                          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),不去除空格
                          

                          # 生成配置文件
                          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
                          

                          [n]<<< word
                          

                          # 基本用法
                          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
                          

                          # 使用 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 通常更快
                          

                          [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
                          

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

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

                          # 示例:同时捕获 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"
                          }
                          

                          [n]<&digit-
                          

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

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

                          [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"
                          

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

                          [n]<>word
                          

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

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

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

                          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}>&-
                          

                          这是 {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}>&-
                          

                          # 方式 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,自动分配
                          

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

                          # 日志系统
                          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}>&-
                          }
                          

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

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

                          # TCP 连接
                          exec 3<>/dev/tcp/www.example.com/80
                          echo -e "GET / HTTP/1.1rnHost: www.example.comrnConnection: closernrn" >&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.1rnHost: $hostrnConnection: closernrn" >&3
                              cat <&3
                              exec 3>&-
                          }
                          http_get "example.com" "/index.html"
                          
                          # UDP 示例(发送数据)
                          echo "test" > /dev/udp/localhost/514   # 发送到本地 syslog
                          
                          # 注意:这些是 Bash 特有功能,不是 POSIX 标准
                          # 某些系统可能需要编译时启用此功能
                          

                          重定向操作符后面的 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"            # 正确,保持为一个单词
                          

                          # 如果展开结果是多个单词,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
                          

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

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

                          #!/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"
                          exec {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"
                          

                          #!/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 "Progress: ${i}0%"    # 显示在终端
                              sleep 1
                          done
                          
                          echo "Process complete"
                          progress "Done!"
                          
                          # 恢复
                          exec 1>&$ORIG_STDOUT 2>&$ORIG_STDERR
                          exec {ORIG_STDOUT}>&- {ORIG_STDERR}>&-
                          

                          #!/bin/bash
                          
                          # 创建临时文件并打开 fd(文件可以立即删除,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"
                          

                          #!/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"
                          

                          # 问题:管道中的循环在子 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"  # 正确的计数
                          

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

                          # 丢弃 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重定向内容请搜索风君子博客以前的文章或继续浏览下面的相关文章希望大家以后多多支持风君子博客!

                          您可能感兴趣的文章:

                          • bash shell 重定向输入和输出的例子

                          站内搜索