在 Linux/Unix 类操作系统中,Shell 重定向是程序员和系统管理员必备的核心技能之一。它允许我们灵活控制命令的输入来源、输出目的地以及错误处理方式,极大地扩展了命令行的使用场景——从简单的日志记录、自动化脚本编写,到复杂的进程间通信、网络连接,重定向都发挥着不可替代的作用。
本文将从文件描述符的底层概念出发,循序渐进地讲解 Shell 重定向的全部核心用法,包括标准流重定向、文件操作、此处文档/字符串、进程替换、管道进阶等内容,并附上完整的语法速查表,帮助你彻底掌握这一强大工具,提升命令行效率。
一、理解文件描述符:重定向的底层基础
在学习重定向之前,我们必须先搞清楚「文件描述符」(File Descriptor,简称 FD)的概念——它是 Shell 与操作系统交互的核心桥梁。
1.1 什么是文件描述符?
文件描述符是操作系统为打开的文件、管道、网络连接等「输入/输出资源」分配的非负整数标识符。Shell 中所有的输入输出操作,本质上都是通过操作文件描述符来完成的。
对于任何一个 Shell 进程,系统会默认分配 3 个标准文件描述符,它们是重定向的基础:
- 0:标准输入(stdin):默认指向键盘,是命令的输入来源(比如
read命令读取用户输入)。 - 1:标准输出(stdout):默认指向终端屏幕,是命令的正常输出(比如
ls命令列出的文件列表)。 - 2:标准错误(stderr):默认指向终端屏幕,是命令的错误输出(比如
ls /nonexistent输出的「没有那个文件或目录」)。
这三个标准流的默认指向可以通过重定向语法修改,比如将 stdout 指向文件、将 stdin 指向某个文本,从而实现灵活的输入输出控制。
1.2 文件描述符的核心特性
- 整数标识:默认使用 0、1、2 表示标准流,用户可自定义 3-9 之间的文件描述符(10 及以上部分 Shell 支持,但兼容性较差)。
- 资源指向:每个文件描述符都对应一个打开的资源(文件、管道、网络连接等),重定向本质就是修改这种「指向关系」。
- 继承性:子进程会继承父进程的文件描述符(比如脚本中的命令会继承脚本进程的重定向设置)。
- 可关闭/复制:用户可以手动关闭自定义文件描述符,或复制一个文件描述符指向另一个资源(类似文件的硬链接)。
理解了文件描述符,我们就能明白所有重定向语法的本质:通过特定符号修改文件描述符的指向,从而改变命令的输入、输出或错误流的目的地。
二、基础重定向:标准流的输入输出控制
基础重定向主要围绕 0(stdin)、1(stdout)、2(stderr)三个标准文件描述符,实现输出写入文件、输入读取文件、丢弃输出等常用操作。
2.1 标准输出重定向(stdout)
标准输出重定向用于将命令的正常输出写入文件,而不是显示在终端上。核心语法有两种:覆盖写入和追加写入。
(1)覆盖写入:cmd > file 或 cmd 1> file
- 功能:将
cmd的 stdout 写入file,如果文件已存在则覆盖原有内容;如果文件不存在则创建。 - 说明:
1是 stdout 的默认文件描述符,cmd > file是cmd 1> file的简写,两者完全等价。 - 示例:
# 将 ls 命令的输出写入 filelist.txt(覆盖原有内容) ls /home/user > filelist.txt # 等价于上面的命令,显式指定文件描述符 1 ls /home/user 1> filelist.txt
(2)追加写入:cmd >> file
- 功能:将
cmd的 stdout 追加到file的尾部,不会覆盖原有内容,只会在文件末尾添加新内容。 - 示例:
# 将当前日期追加到 log.txt 文件尾部 date >> log.txt # 多次执行会持续追加,不会覆盖之前的日志 echo "执行了备份操作" >> log.txt
2.2 标准错误重定向(stderr)
标准错误重定向用于单独处理命令的错误输出,比如将错误信息写入日志文件,避免干扰正常输出。
(1)覆盖写入:cmd 2> file
- 功能:将
cmd的 stderr 写入file,覆盖原有内容(文件不存在则创建)。 - 示例:
# 尝试读取不存在的文件,将错误信息写入 error.log cat /nonexistent/file 2> error.log # 查看错误日志 cat error.log # 输出:cat: /nonexistent/file: 没有那个文件或目录
(2)追加写入:cmd 2>> file
- 功能:将
cmd的 stderr 追加到file尾部,不覆盖原有内容。 - 示例:
# 循环执行错误命令,将所有错误信息追加到 error.log for i in {1..5}; do cat /nonexistent/file$i 2>> error.log done
2.3 同时重定向 stdout 和 stderr
在实际使用中,我们经常需要将命令的正常输出和错误输出同时写入同一个文件(比如完整记录脚本运行日志),有两种常用语法。
(1)简洁语法:cmd &> file(bash 特有)
- 功能:将 stdout 和 stderr 同时写入
file,覆盖原有内容;使用&>> file可实现追加。 - 示例:
# 将备份脚本的所有输出(正常+错误)写入 backup.log(覆盖) ./backup.sh &> backup.log # 追加模式:将输出添加到日志文件尾部 ./backup.sh &>> backup.log
(2)兼容语法:cmd > file 2>&1(跨 Shell 支持)
- 功能:与
&> file效果相同,但兼容性更好(支持 bash、sh、zsh 等所有 Shell)。 - 关键注意点:重定向顺序至关重要!
- 正确写法:
cmd > file 2>&1(先将 stdout 指向 file,再将 stderr 复制到 stdout 的指向,最终两者都写入 file)。 - 错误写法:
cmd 2>&1 > file(先将 stderr 复制到 stdout 的默认指向(终端),再将 stdout 指向 file,最终只有 stdout 写入 file,stderr 仍输出到终端)。
- 正确写法:
- 示例:
# 兼容写法,将 stdout 和 stderr 同时写入 log.txt ./script.sh > log.txt 2>&1 # 追加模式:在兼容写法中,追加符号需加在 stdout 重定向上 ./script.sh >> log.txt 2>&1
2.4 丢弃输出:重定向到 /dev/null
/dev/null 是 Linux 中的「空设备文件」,它会丢弃所有写入它的数据。常用于屏蔽命令的输出或错误信息。
(1)丢弃 stdout:cmd > /dev/null
- 功能:执行命令,但不显示正常输出(错误输出仍会显示)。
- 示例:
# 安装软件时,屏蔽正常的进度输出,只显示错误信息 sudo apt install nginx > /dev/null
(2)丢弃 stderr:cmd 2> /dev/null
- 功能:执行命令,屏蔽错误信息(正常输出仍会显示)。
- 示例:
# 查找文件时,忽略「权限不够」的错误提示 find / -name "config.json" 2> /dev/null
(3)完全静默运行:cmd &> /dev/null
- 功能:同时丢弃 stdout 和 stderr,命令执行时不产生任何输出(完全静默)。
- 示例:
# 后台运行脚本,不输出任何信息 ./background_task.sh &> /dev/null &
2.5 标准输入重定向(stdin)
标准输入重定向用于将文件内容作为命令的输入,而不是从键盘读取输入。核心语法是 cmd < file。
- 功能:将
file的内容作为cmd的 stdin(标准输入)。 - 适用场景:命令本身支持从 stdin 读取数据(比如
cat、grep、awk、read等)。 - 示例:
# 用 cat 命令读取文件(等价于 cat file.txt,但展示了输入重定向) cat < file.txt # 用 grep 从文件中查找关键词(等价于 grep "hello" file.txt) grep "hello" < file.txt # 用 read 命令从文件中读取一行内容赋值给变量 read line < file.txt echo $line # 输出 file.txt 的第一行内容 # 批量导入数据:将 data.txt 的内容作为 mysql 命令的输入 mysql -u root -p < data.txt
三、高级输入:此处文档与此处字符串
除了从文件读取输入,Shell 还支持通过「此处文档」(Here-document)和「此处字符串」(Here-string)直接将字符串(单行或多行)作为命令的 stdin,无需创建临时文件。
3.1 此处文档(Here-document):cmd << EOL ... EOL
此处文档用于将多行文本作为命令的输入,适用于需要输入多行内容的场景(比如编写配置文件、批量执行命令等)。
(1)基本语法
cmd << EOL
第一行文本
第二行文本
变量或命令会被展开:$HOME $(date)
EOL
- 核心规则:
- 起始标记:
<< EOL(EOL 是自定义标记,可替换为任意字符串,如<< END、<< MARKER)。 - 结束标记:单独一行书写的
EOL(前后不能有空格,否则无法识别)。 - 变量展开:默认情况下,文本中的变量(如
$HOME)、命令替换(如$(date))会被 Shell 展开。
- 起始标记:
- 示例:
# 用 cat 命令创建多行文件(无需手动编辑,直接写入内容) cat > config.conf << EOL # 配置文件:$(date) 生成 username=$USER home_dir=$HOME log_level=info EOL # 查看生成的文件(变量已展开) cat config.conf # 输出: # # 配置文件:2024年5月20日 14:30:00 生成 # username=user # home_dir=/home/user # log_level=info # 批量执行 ssh 命令(无需交互输入多行命令) ssh user@remote-host << EOL echo "开始执行远程操作" ls /home/user date >> /tmp/remote_log.txt EOL
(2)原样解析:引号包裹结束标记
如果希望文本中的变量、命令替换不被展开(原样保留),可以用引号(单引号或双引号)包裹结束标记。
语法:
cmd << "EOL" # 或 'EOL'
变量不会被展开:$HOME $(date)
EOL
示例:
cat > script.sh << "EOL"
#!/bin/bash
echo "当前用户:$USER" # 变量 $USER 会在脚本运行时展开,而非此处文档生成时
echo "当前日期:$(date)"
EOL
# 查看生成的脚本(变量和命令替换原样保留)
cat script.sh
# 输出:
# #!/bin/bash
# echo "当前用户:$USER"
# echo "当前日期:$(date)"
(3)忽略行首制表符:cmd <<- EOL
如果希望此处文档的多行文本可以缩进(便于代码格式化),可以在起始标记前加 -,此时行首的制表符(Tab)会被自动忽略(空格不忽略)。
语法:
cmd <<- EOL
行首的制表符会被忽略
多行缩进也可以
EOL
示例:
# 代码中使用缩进,生成的文件无缩进(更整洁)
cat > nginx.conf <<- EOL
server {
listen 80;
server_name example.com;
root /var/www/html;
}
EOL
# 查看生成的文件(行首制表符已被移除)
cat nginx.conf
# 输出:
# server {
# listen 80;
# server_name example.com;
# root /var/www/html;
# }
3.2 此处字符串(Here-string):cmd <<< "string"
此处字符串是此处文档的简化版,用于将单行字符串作为命令的输入,语法更简洁。
- 语法:
cmd <<< "string"(字符串可加引号,也可不加,但含空格时必须加)。 - 适用场景:只需输入单行内容,比此处文档更高效(无需写结束标记)。
- 示例:
# 用 grep 查找字符串中的关键词(等价于 echo "hello world" | grep "world") grep "world" <<< "hello world" # 输出:hello world # 用 awk 处理单行数据 awk -F ',' '{print $2}' <<< "apple,banana,orange" # 输出:banana # 用 read 命令读取单行字符串赋值给变量 read name age <<< "Alice 25" echo "姓名:$name,年龄:$age" # 输出:姓名:Alice,年龄:25 # 传递含空格的字符串(必须加引号) echo <<< "Hello, this is a here-string" # 输出:Hello, this is a here-string
四、自定义文件描述符:扩展输入输出能力
除了 0、1、2 三个标准文件描述符,Shell 允许用户自定义 3-9 之间的文件描述符,用于同时操作多个文件或资源(比如同时读取多个文件、写入多个日志等)。
4.1 自定义文件描述符的基本操作
自定义文件描述符的操作主要通过 exec 命令实现(exec 用于修改当前 Shell 进程的文件描述符配置),核心操作包括:打开文件、关闭文件、复制文件描述符。
(1)打开文件(读写模式)
- 只读模式:
exec 3< file(用描述符 3 打开文件,只能读取)。 - 只写模式:
exec 3> file(用描述符 3 打开文件,只能写入,覆盖原有内容)。 - 读写模式:
exec 3<> file(用描述符 3 打开文件,可读写)。 - 示例:
# 用描述符 3 只读打开文件 exec 3< file.txt # 从描述符 3 读取内容(用 cat <&3) cat <&3 # 输出 file.txt 的内容 # 关闭描述符 3(用完必须关闭,避免资源泄漏) exec 3>&- # 用描述符 4 只写打开文件(覆盖) exec 4> output.txt # 向描述符 4 写入内容(用 echo "content" >&4) echo "自定义描述符写入" >&4 echo "多行内容" >&4 # 关闭描述符 4 exec 4>&- # 查看写入的内容 cat output.txt # 输出:自定义描述符写入、多行内容
(2)关闭文件描述符:exec 3>&-
- 功能:关闭自定义文件描述符 3(释放对应的文件资源)。
- 注意:自定义文件描述符如果不手动关闭,会一直占用资源,直到当前 Shell 会话结束。因此,使用完后必须关闭。
(3)复制文件描述符:exec 4>&3
- 功能:将文件描述符 3 的指向复制给 4,此时 4 和 3 指向同一个文件/资源(相当于「硬链接」)。
- 衍生用法:
exec 4>&3-(复制后关闭原描述符 3)。 - 示例:
# 打开文件并复制描述符 exec 3< file.txt # 3 只读打开 file.txt exec 4>&3 # 4 复制 3 的指向(也指向 file.txt) # 从 3 读取内容(会移动文件指针) head -n 1 <&3 # 输出第一行 # 从 4 读取内容(文件指针已被 3 移动,所以从第二行开始) cat <&4 # 输出第二行及以后的内容 # 关闭描述符 exec 3>&- exec 4>&- # 复制后关闭原描述符(常用场景:传递文件描述符后释放原资源) exec 5> log.txt exec 6>&5- # 6 复制 5 的指向,然后关闭 5 echo "通过复制的描述符写入" >&6 exec 6>&-
4.2 自定义文件描述符的实际应用场景
(1)同时读写多个文件
# 打开两个日志文件,分别写入不同类型的日志
exec 3> info.log # 普通日志
exec 4> error.log # 错误日志
# 向不同描述符写入内容
echo "[$(date)] 程序启动成功" >&3
echo "[$(date)] 数据库连接失败" >&4
# 关闭描述符
exec 3>&-
exec 4>&-
(2)永久重定向当前会话的输出
# 永久重定向当前会话的 stderr 到 error.log(所有后续命令的错误都写入该文件)
exec 2> error.log
# 后续命令的错误会自动写入 error.log,不再显示在终端
ls /nonexistent # 错误信息写入 error.log
cat /invalid/file # 错误信息写入 error.log
# 取消永久重定向(将 stderr 恢复指向终端)
exec 2>&1
(3)通过文件描述符实现网络通信
Shell 支持通过 /dev/tcp/host/port 和 /dev/udp/host/port 打开网络连接,并通过自定义文件描述符进行读写(相当于简单的网络客户端)。
示例:测试端口连通性(替代 telnet 或 nc)
# 打开到 example.com 80 端口的 TCP 连接(描述符 3)
exec 3<> /dev/tcp/example.com/80
# 向服务器发送 HTTP 请求(通过描述符 3 写入)
echo -e "GET / HTTP/1.1\r\nHost: example.com\r\nConnection: close\r\n\r\n" >&3
# 读取服务器响应(通过描述符 3 读取)
cat <&3
# 关闭连接
exec 3>&-
示例:UDP 端口测试
# 打开到 127.0.0.1 53 端口的 UDP 连接
exec 3<> /dev/udp/127.0.0.1/53
# 发送 UDP 数据包(DNS 查询示例,需配合 DNS 协议格式,此处仅演示)
echo -n -e "\x00\x01\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00\x03\x77\x77\x77\x06\x67\x6f\x6f\x67\x6c\x65\x03\x63\x6f\x6d\x00\x00\x01\x00\x01" >&3
# 读取响应
cat <&3
# 关闭连接
exec 3>&-
五、高级重定向:进程替换与管道进阶
5.1 进程替换(Process Substitution)
进程替换是 Shell 的高级特性,它允许将一个命令的输出(或输入)作为另一个命令的「文件参数」,本质是创建一个匿名的命名管道(FIFO)来传递数据。
核心语法有四种:
- 标准输出替换:
cmd <(cmd1)(将cmd1的 stdout 作为cmd的文件参数)。 - 标准输入替换:
cmd >(cmd2)(将cmd2的 stdin 作为cmd的文件参数)。 - 多进程替换:
cmd <(cmd1) <(cmd2)(多个命令的 stdout 作为cmd的参数)。 - 结合 stdin 重定向:
cmd < <(cmd1)(将cmd1的 stdout 作为cmd的 stdin)。
(1)基础用法:对比两个命令的输出
进程替换最常用的场景是对比两个命令的输出(比如对比两个目录的文件列表、两个日志文件的差异)。
示例:对比两个目录的文件列表
# 查找 /path1 和 /path2 的文件列表,排序后对比差异
diff <(find /path1 | sort) <(find /path2 | sort)
- 原理:
find /path1 | sort的输出被写入一个匿名 FIFO。find /path2 | sort的输出被写入另一个匿名 FIFO。diff命令接收这两个 FIFO 的路径作为参数,对比它们的内容。
- 等价于(但无需创建临时文件):
find /path1 | sort > temp1.txt find /path2 | sort > temp2.txt diff temp1.txt temp2.txt rm temp1.txt temp2.txt
(2)多进程替换:传递多个输入源
示例:用 awk 同时处理两个命令的输出
# 统计两个日志文件中包含 "error" 的行数,并对比
awk '
NR==FNR { count1++ } # 处理第一个输入(<(grep "error" log1.txt))
NR!=FNR { count2++ } # 处理第二个输入(<(grep "error" log2.txt))
END {
print "log1.txt 错误行数:" count1
print "log2.txt 错误行数:" count2
}
' <(grep "error" log1.txt) <(grep "error" log2.txt)
(3)反向进程替换:cmd1 >(cmd2)
cmd1 >(cmd2) 表示将 cmd2 的 stdin 作为 cmd1 的输出文件参数,即 cmd1 的输出写入匿名 FIFO,cmd2 从 FIFO 读取。
示例:同时保存输出到文件和显示在终端(替代 tee 命令)
# 将 ls 的输出同时写入 filelist.txt 和通过 grep 过滤显示
ls /home/user >(grep "doc" > filtered.txt) > filelist.txt
- 原理:
ls /home/user的 stdout 被重定向到两个地方:filelist.txt和匿名 FIFO(grep "doc"从 FIFO 读取)。grep "doc"过滤出包含 “doc” 的行,写入filtered.txt。
(4)结合 stdin 重定向:cmd < <(cmd1)
这种写法等价于 cmd1 | cmd(管道),但在某些场景下更灵活(比如需要同时传递多个输入)。
示例:
# 等价于 cat file.txt | grep "hello"
grep "hello" < <(cat file.txt)
# 更实用的场景:同时传递两个输入
diff <(sort file1.txt) < <(sort file2.txt) # 等价于 diff <(sort file1.txt) <(sort file2.txt)
5.2 管道进阶:| 与 |&
管道(|)是最常用的重定向工具之一,它的本质是将前一个命令的 stdout 重定向到后一个命令的 stdin。Shell 还提供了错误管道(|&),用于同时重定向 stdout 和 stderr。
(1)普通管道:cmd1 | cmd2
- 功能:
cmd1的 stdout →cmd2的 stdin。 - 注意:管道默认只传递 stdout,stderr 仍会输出到终端。如果需要传递 stderr,需结合
2>&1。 - 示例:
# 过滤 ls 的输出,只显示 .txt 文件 ls /home/user | grep "\.txt" # 传递 stderr 到管道(过滤错误信息) find / -name "config.json" 2>&1 | grep -v "权限不够" # 排除「权限不够」的错误
(2)错误管道:cmd1 |& cmd2(bash 4.0+)
- 功能:将
cmd1的 stdout 和 stderr 同时传递到cmd2的 stdin(等价于cmd1 2>&1 | cmd2)。 - 适用场景:需要同时处理一个命令的正常输出和错误输出。
- 示例:
# 同时过滤正常输出和错误输出 find / -name "config.json" |& grep -E "config.json|权限" # 显示找到的文件和权限错误 # 等价于兼容写法 find / -name "config.json" 2>&1 | grep -E "config.json|权限"
(3)管道退出状态:${PIPESTATUS[@]}
默认情况下,Shell 只返回管道中最后一个命令的退出状态($?)。如果需要获取所有管道命令的退出状态,可以使用 ${PIPESTATUS[@]} 数组(bash 特有)。
- 功能:
${PIPESTATUS[@]}存储管道中每个命令的退出码(按命令顺序排列)。 - 退出码规则:0 表示成功,非 0 表示失败。
- 示例:
# 执行一个包含失败命令的管道 ls /nonexistent | grep "file" | sort # 查看所有命令的退出状态 echo ${PIPESTATUS[@]} # 输出:2 1 0 # 解释: # ls /nonexistent 失败(退出码 2) # grep "file" 没有匹配到内容(退出码 1) # sort 成功执行(退出码 0) # 捕获每个命令的退出码 ls /nonexistent | grep "file" | sort status=("${PIPESTATUS[@]}") echo "ls 退出码:${status[0]}" echo "grep 退出码:${status[1]}" echo "sort 退出码:${status[2]}"
5.3 多命令重定向:子shell与大括号
如果需要将多个命令的输出统一重定向到文件,可以使用「子shell」或「大括号」语法,避免重复写重定向符号。
(1)子shell:(cmd1; cmd2) > file
- 功能:创建一个子shell,在子shell中执行
cmd1和cmd2,将所有命令的 stdout 统一重定向到file。 - 特点:子shell中的变量修改、环境变量设置不会影响父shell。
- 示例:
# 将多个命令的输出统一写入 log.txt ( echo "=== 脚本开始执行:$(date) ===" ls /home/user echo "=== 脚本执行结束 ===" ) > log.txt # 查看日志 cat log.txt
(2)大括号:{ cmd1; cmd2; } > file
- 功能:与子shell类似,但不创建子shell(直接在当前shell中执行命令),效率更高。
- 关键注意点:
{后必须有空格。}前必须有分号(;)或换行。- 命令执行后的变量修改、环境变量设置会影响当前shell。
- 示例:
# 不创建子shell,统一重定向输出 { echo "=== 系统信息:$(date) ===" uname -a free -h } >> system.log # 追加模式 # 变量修改会影响当前shell { name="Alice" age=25 } > /dev/null echo $name $age # 输出:Alice 25(变量已生效)
六、分流输出:同时处理 stdout 和 stderr
在某些场景下,我们需要将一个命令的 stdout 和 stderr 分别传递给不同的命令处理(比如 stdout 写入日志,stderr 发送邮件报警),这时可以结合重定向和进程替换实现分流。
核心语法:cmd > >(cmd1) 2> >(cmd2)
- 功能:将
cmd的 stdout 传递给cmd1,stderr 传递给cmd2(两个命令并行处理)。 - 示例:
# 备份数据库:stdout 写入日志,stderr 发送邮件报警
mysqldump -u root -p database > >(tee backup.log) 2> >(mail -s "备份失败" admin@example.com)
# 解释:
# mysqldump 的 stdout → tee backup.log(同时写入文件和终端)
# mysqldump 的 stderr → mail 命令(发送报警邮件)
# 另一个示例:stdout 写入文件,stderr 过滤后显示
./script.sh > output.log 2> >(grep -E "ERROR|WARNING" >&2)
# 解释:
# stdout 写入 output.log
# stderr 过滤出 ERROR/WARNING 行,并重定向到终端(>&2 表示 stderr)
七、Shell 重定向语法速查表
| 重定向命令 | 描述 |
|---|---|
cmd > file |
将 cmd 的标准输出(stdout)重定向到文件(覆盖文件内容) |
cmd 1> file |
与 cmd > file 相同,1 是 stdout 的默认文件描述符 |
cmd 2> file |
将 cmd 的标准错误(stderr)重定向到文件(覆盖文件内容),2 是 stderr 的默认文件描述符 |
cmd >> file |
将 cmd 的 stdout 追加到文件尾部(不覆盖原有内容) |
cmd 2>> file |
将 cmd 的 stderr 追加到文件尾部(不覆盖原有内容) |
cmd &> file |
将 cmd 的 stdout 和 stderr 同时重定向到文件(覆盖文件内容) |
cmd > file 2>&1 |
另一种将 stdout 和 stderr 重定向到文件的方式(覆盖文件内容) ⚠️ 注意:与 cmd 2>&1 > file 不同,重定向顺序至关重要! |
cmd > /dev/null |
丢弃 cmd 的 stdout(不输出任何内容) |
cmd 2> /dev/null |
丢弃 cmd 的 stderr(不输出错误信息) |
cmd &> /dev/null |
同时丢弃 cmd 的 stdout 和 stderr(完全静默运行) |
cmd < file |
将文件内容作为 cmd 的标准输入(stdin) |
cmd << EOL<br>foo<br>bar<br>baz<br>EOL |
此处文档(here-document):将多行文本作为 cmd 的 stdin如果 EOL 用引号括起(如 << "EOL"),文本将被原样解析(不展开变量/命令) |
cmd <<- EOL<br> foo<br> bar<br>EOL |
带制表符忽略的此处文档:与普通此处文档类似,但会忽略行首的制表符(空格不忽略),便于格式化代码 |
cmd <<< "string" |
此处字符串(here-string):将单行字符串作为 cmd 的 stdin |
exec 2> file |
永久重定向所有命令的 stderr 到文件(当前终端会话生效) |
exec 3< file |
使用自定义文件描述符 3 打开文件(只读模式) |
exec 3> file |
使用自定义文件描述符 3 打开文件(只写模式,覆盖原有内容) |
exec 3<> file |
使用自定义文件描述符 3 打开文件(读写模式) |
exec 3>&- |
关闭自定义文件描述符 3 |
exec 4>&3 |
将文件描述符 3 复制到 4(4 成为 3 的副本,指向同一文件/流) |
exec 4>&3- |
将文件描述符 3 复制到 4 后,关闭文件描述符 3 |
echo "foo" >&3 |
向自定义文件描述符 3 写入内容 |
cat <&3 |
从自定义文件描述符 3 读取内容 |
(cmd1; cmd2) > file |
使用子shell将多个命令的 stdout 统一重定向到文件 |
{ cmd1; cmd2; } > file |
不使用子shell(效率更高),将多个命令的 stdout 统一重定向到文件 ⚠️ 注意: { 后和 } 前必须有空格,且命令末尾需加 ; |
exec 3<> /dev/tcp/host/port |
打开到 host:port 的 TCP 连接,使用文件描述符 3 进行读写 |
exec 3<> /dev/udp/host/port |
打开到 host:port 的 UDP 连接,使用文件描述符 3 进行读写 |
cmd <(cmd1) |
进程替换:将 cmd1 的 stdout 重定向到匿名 FIFO(命名管道),并将 FIFO 路径作为参数传递给 cmd适用于 cmd 不直接读取 stdin 的场景 |
cmd < <(cmd1) |
进程替换+stdin重定向:将 cmd1 的 stdout 重定向到匿名 FIFO,再将 FIFO 作为 cmd 的 stdin示例:`diff <(find /path1 |
cmd <(cmd1) <(cmd2) |
多进程替换:将 cmd1 和 cmd2 的 stdout 分别重定向到两个匿名 FIFO,均作为参数传递给 cmd |
cmd1 >(cmd2) |
进程替换(反向):运行 cmd2 并将其 stdin 连接到匿名 FIFO,将 FIFO 路径作为参数传递给 cmd1即 cmd1 的输出写入 FIFO,cmd2 从 FIFO 读取 |
cmd1 | cmd2 |
管道:将 cmd1 的 stdout 重定向到 cmd2 的 stdin💡 进阶:与 cmd1 > >(cmd2)、cmd2 < <(cmd1) 效果相同 |
cmd1 |& cmd2 |
错误管道(bash 4.0+):将 cmd1 的 stdout 和 stderr 同时重定向到 cmd2 的 stdin旧版本 bash 需使用 cmd1 2>&1 | cmd2 |
cmd | tee file |
分流输出:将 cmd 的 stdout 同时输出到屏幕和文件(覆盖文件内容)使用 tee -a file 可实现追加模式 |
exec {filew}> file |
命名文件描述符(bash 4.1+):使用命名变量 filew 打开文件(只写模式),避免硬编码数字描述符 |
cmd 3>&1 1>&2 2>&3 |
交换 stdout 和 stderr:通过临时文件描述符 3 实现两者互换 |
cmd > >(cmd1) 2> >(cmd2) |
分流 stdout/stderr:将 cmd 的 stdout 传递给 cmd1,stderr 传递给 cmd2(并行处理) |
cmd1 | cmd2 | cmd3 | cmd4<br>echo ${PIPESTATUS[@]} |
管道退出状态:${PIPESTATUS[@]} 数组存储所有管道命令的退出码(按顺序排列),默认仅返回最后一个命令的退出码 |
八、总结与最佳实践
Shell 重定向是命令行效率的「倍增器」,掌握它可以让你轻松应对日志记录、自动化脚本、进程间通信等复杂场景。以下是核心总结和最佳实践:
核心总结
- 文件描述符是基础:记住 0(stdin)、1(stdout)、2(stderr)三个标准描述符,自定义描述符使用 3-9。
- 重定向顺序很关键:
cmd > file 2>&1是正确的,cmd 2>&1 > file是错误的(顺序颠倒会导致 stderr 无法重定向到文件)。 - 按需选择工具:
- 单行输入用「此处字符串」(
<<<),多行输入用「此处文档」(<< EOL)。 - 对比命令输出用「进程替换」(
<(cmd)),避免创建临时文件。 - 同时处理多个文件用「自定义文件描述符」。
- 单行输入用「此处字符串」(
- 资源释放:自定义文件描述符使用后必须关闭(
exec 3>&-),避免资源泄漏。
最佳实践
- 日志记录:脚本中使用
exec 2> error.log永久重定向 stderr,或./script.sh &>> log.txt记录所有输出。 - 静默执行:后台运行的命令使用
&> /dev/null屏蔽输出,避免干扰终端。 - 批量操作:使用此处文档批量生成配置文件、执行远程命令,无需手动编辑。
- 错误处理:通过
|&或2>&1 |传递 stderr,结合grep过滤错误信息,或发送报警邮件。 - 兼容性考虑:跨 Shell 脚本优先使用
cmd > file 2>&1而非&> file,使用cmd 2>&1 | cmd2而非|&。
通过本文的学习,相信你已经掌握了 Shell 重定向的全部核心用法。在实际使用中,建议多尝试将这些语法结合起来,比如「自定义文件描述符 + 进程替换 + 管道」,可以实现更复杂的需求。记住,最好的学习方式是实践——多写脚本、多测试,才能真正将这些知识内化为自己的技能。
原文 https://blog.csdn.net/2301_79518550/article/details/144012854