Shell 输入/输出重定向详解(新手详细版)
本文档面向零基础新手,从“标准输入、标准输出、标准错误是什么”讲起,详细说明重定向符号、覆盖与追加、合并错误与输出、管道、here 文档、常见用法等,并配有大量示例。
一、先弄清:程序的三条“标准流”
1.1 程序默认从哪里读、往哪里写?
在 Shell 里,一条命令(程序)默认有三个通道和外界打交道:
| 名称 | 全称 | 文件描述符编号 | 默认来源/去向 | 常见用途 |
|---|---|---|---|---|
| 标准输入 | stdin | 0 | 键盘 | 读用户输入、读管道左侧输出 |
| 标准输出 | stdout | 1 | 终端屏幕 | 正常结果、echo 输出 |
| 标准错误 | stderr | 2 | 终端屏幕 | 错误信息、警告 |
- 标准输入(0):程序“读”的数据,默认是键盘;也可以被重定向成文件或管道。
- 标准输出(1):程序“正常打印”的内容,默认显示在终端;可以重定向到文件或管道。
- 标准错误(2):程序“报错、警告”的内容,默认也显示在终端;可以单独或和标准输出一起重定向。
为什么要分开 1 和 2?
这样可以把“正常结果”和“错误信息”分开处理,例如:正常结果写进文件,错误信息仍显示在屏幕上,或都写进同一个日志文件。
1.2 “重定向”在做什么?
重定向就是改变这些“通道”的来源或去向:
- 把本应输出到屏幕的内容,改写到文件;
- 把本应从键盘读的输入,改成从文件或一段文字读;
- 把错误单独写到另一个文件,或和正常输出合并到一起。
下面按“输出重定向”“输入重定向”“管道”“here 文档”等分别说明。
二、输出重定向:把输出写到文件
2.1 覆盖写入:>
命令 > 文件 表示:把命令的标准输出(1)重定向到文件;若文件已存在则先清空再写(覆盖)。
echo "第一行" > out.txt
cat out.txt
# 第一行
echo "第二行" > out.txt
cat out.txt
# 第二行(原来的“第一行”被覆盖了)
注意:> 只重定向标准输出,不重定向标准错误;错误信息仍会出现在屏幕上。
ls /存在 不存在的目录 > list.txt
# 终端上仍会看到“不存在的目录”的报错;list.txt 里只有“存在的目录”那一行的结果
2.2 追加写入:>>
命令 >> 文件 表示:把命令的标准输出追加到文件末尾,不删掉原有内容。
echo "第一行" >> log.txt
echo "第二行" >> log.txt
cat log.txt
# 第一行
# 第二行
常用于日志:每次运行都把新内容接到文件后面。
2.3 只重定向标准错误:2>、2>>
- 命令 2> 文件:只把标准错误(2)写到文件,标准输出仍到屏幕;文件会被覆盖。
- 命令 2>> 文件:只把标准错误追加到文件。
# 把错误信息写进 err.txt,正常输出仍在屏幕
ls /不存在的路径 2> err.txt
# 追加错误到 err.log
some_cmd 2>> err.log
为什么是 2>?
“2”表示文件描述符 2(标准错误);“>”表示重定向到文件。合起来就是“把 2 重定向到文件”。
2.4 标准输出和标准错误都重定向
写法一:分别重定向到不同文件
# 正常输出到 out.txt,错误到 err.txt
ls /tmp /不存在的 1> out.txt 2> err.txt
写法二:都写到同一个文件(常见)
要把“正常输出”和“错误”都写进同一个文件,需要先把 2 重定向到 1 的当前去向,再重定向 1 到文件。顺序很重要:
# 正确:先定好 1 的去向(文件),再把 2 指向 1 的去向
ls /tmp /不存在的 > all.txt 2>&1
# 错误示例:2>&1 > file
# 这时 2 指向“当时的 1”(还是屏幕),然后 1 才改到 file,错误仍会到屏幕
简写(Bash):
# 都覆盖写入 file
ls /tmp /不存在的 &> all.txt
# 或
ls /tmp /不存在的 >& all.txt
# 都追加到 file(Bash)
ls /tmp /不存在的 >> all.txt 2>&1
小结:
- > file 2>&1:标准输出和标准错误都覆盖写到 file。
- >> file 2>&1:都追加到 file。
- &> file(Bash):都覆盖写到 file。
2.5 丢弃输出:/dev/null
/dev/null 是“黑洞”设备:写进去的内容会被丢弃,读出来是“空”。
常用于“不想要任何输出”时:
# 不关心正常输出,也不关心错误,都丢掉
some_cmd > /dev/null 2>&1
# 或
some_cmd &> /dev/null
# 只丢掉错误,正常输出仍显示
some_cmd 2> /dev/null
三、输入重定向:从文件或字符串读入
3.1 从文件读入:<
命令 < 文件 表示:把命令的标准输入(0)改成从文件读,而不是从键盘。
# 把 file.txt 的内容当作 wc 的输入
wc -l < file.txt
# 输出 file.txt 的行数
# sort 从 data.txt 读入并排序,结果打印到屏幕
sort < data.txt
和“把文件当参数”的区别:
- 命令 < 文件:文件内容作为输入流,程序通过“读标准输入”得到。
- 命令 文件:文件名作为参数,程序自己打开文件读(如 cat file.txt)。
3.2 Here 文档:<< 标记
<< 标记 表示:从脚本里的“下一行”开始,直到遇到单独一行的“标记”为止,这一段文字都当作标准输入。
适合在脚本里嵌入多行输入,不必先写进文件。
基本语法:
命令 << 结束标记
第一行内容
第二行内容
...
结束标记
示例:
cat << EOF
第一行
第二行
第三行
EOF
# 输出:
# 第一行
# 第二行
# 第三行
“结束标记”可以是任意字符串(如 EOF、END、STOP),只要单独成行且前后无空格即可。
若结束标记加引号(如 << 'EOF'),则中间内容不会做变量替换、命令替换;不加引号则会展开 $变量、$(命令)。
name="张三"
cat << EOF
你好,$name
EOF
# 你好,张三
cat << 'EOF'
你好,$name
EOF
# 你好,$name(字面)
3.3 Here 文档缩进:<<-
<<- 标记 表示:前导的 Tab 会被忽略,这样可以在脚本里把 here 文档缩进写,结束标记前也可以有 Tab(不能是空格)。
cat <<- EOF
第一行
第二行
EOF
3.4 Here 字符串:<<<(Bash)
<<< 字符串 表示:把这一小段字符串直接当作命令的标准输入(单行时比 here 文档简洁)。
wc -l <<< "a
b
c"
# 3
grep "hello" <<< "hello world"
# hello world
四、文件描述符与重定向(了解即可)
- n>:把文件描述符 n 重定向到文件(覆盖)。
- n>>:追加。
- n<:从文件读入到文件描述符 n。
- &n:表示“当前文件描述符 n 的去向”;2>&1 即“把 2 指向 1 的当前去向”。
- &-:关闭该描述符。
示例:
# 1 和 2 都到 file(前面已讲过)
cmd > file 2>&1
# 从 file 读入到标准输入(等价于 < file)
cmd 0< file
日常脚本里掌握 >、>>、2>、2>&1、<、<<、<<< 即可。
五、管道:|
5.1 管道做什么?
命令1 | 命令2 表示:把命令1 的标准输出接到命令2 的标准输入;命令1 的标准错误默认不进管道,仍显示在终端。
ls -l | grep ".txt"
# ls 的输出作为 grep 的输入,grep 再筛选含 .txt 的行
cat file.txt | sort
# file.txt 的内容经 cat 输出,再作为 sort 的输入
5.2 让错误也进管道:2>&1 | …
若希望标准错误也参与管道,需要先把 2 合并到 1,再管道:
some_cmd 2>&1 | grep "error"
5.3 管道和重定向一起用
# 结果写文件,错误丢掉
ls -l | grep txt > result.txt 2>/dev/null
# 前一个命令输出写文件,同时再管道给下一个命令用 tee
ls -l | tee list.txt | wc -l
六、tee:一边写文件一边继续管道
tee 文件 会从标准输入读入,同时:① 写入指定文件,② 再原样输出到标准输出(可继续管道)。
# 输出既保存到 out.txt,又显示在屏幕
ls -l | tee out.txt
# 保存到文件,并继续管道给 wc
ls -l | tee list.txt | wc -l
tee -a 表示追加到文件,不覆盖。
echo "新行" | tee -a log.txt
七、进程替换(Bash):(…)
- <(<(命令)):把命令的输出当作一个“文件名”传给需要文件的命令(实际是临时管道/设备)。
- >(命令):把当前命令的输入接到另一个命令的输入(少用)。
示例:
# diff 需要两个“文件”,用两个命令的输出当“文件”
diff <(ls dir1) <(ls dir2)
# 把内容写入“某个命令的输入”
echo "hello" > >(cat)
了解即可,初学先掌握普通重定向和管道。
八、重定向顺序的影响
重定向是从左到右处理的;2>&1 表示“把 2 指向 1 的当前去向”。所以:
- > file 2>&1:先把 1 改到 file,再把 2 指向 1(即 file)→ 都对。
- 2>&1 > file:先把 2 指向 1(当时还是屏幕),再把 1 改到 file → 错误仍在屏幕,只有正常输出到 file。
建议:要“都进同一文件”时,写 > file 2>&1 或 >> file 2>&1。
九、实用示例汇总
9.1 只保存正常输出
ls -l > list.txt
9.2 只保存错误
my_script 2> error.log
9.3 正常和错误都进同一文件(覆盖/追加)
my_script > all.log 2>&1
my_script >> all.log 2>&1
9.4 完全静默(不想要任何输出)
my_script > /dev/null 2>&1
# 或
my_script &> /dev/null
9.5 从文件读入
sort < data.txt
while read -r line; do echo "$line"; done < file.txt
9.6 在脚本里嵌入多行输入(here 文档)
mysql -u user -p << EOF
SELECT * FROM t LIMIT 1;
EOF
9.7 单行字符串当输入(here 字符串)
grep "key" <<< "key=value"
9.8 既保存又显示(tee)
./install.sh | tee install.log
9.9 管道 + 过滤
ps aux | grep nginx
cat access.log | grep " 200 " | wc -l
十、常见坑与建议
- > 会覆盖文件:想保留原内容用 >>。
- 2>&1 的顺序:要“都进同一文件”时,先重定向 1 再写 2>&1。
- 管道只传标准输出:错误要进管道需先 2>&1 再 |。
- here 文档的结束标记要单独一行,且前后不要有多余空格。
- 变量/命令替换:here 文档不用引号会展开,用 ‘EOF’ 则不展开。
十一、符号速查表
| 写法 | 含义 |
|---|---|
| > file | 标准输出覆盖写到 file |
| >> file | 标准输出追加到 file |
| 2> file | 标准错误覆盖写到 file |
| 2>> file | 标准错误追加到 file |
| > file 2>&1 | 标准输出和错误都覆盖写到 file |
| >> file 2>&1 | 都追加到 file |
| &> file | 都覆盖写到 file(Bash) |
| < file | 从 file 读入标准输入 |
| << 标记 | here 文档(到“标记”行为止) |
| <<< 字符串 | here 字符串(Bash) |
| | | 管道:前命令标准输出 → 后命令标准输入 |
| tee file | 从标准输入读,写 file 并继续输出 |
| /dev/null | 丢弃输出或读入空 |
十二、小结
- 三条流:标准输入(0)、标准输出(1)、标准错误(2);默认分别是键盘和屏幕。
- 输出重定向:> 覆盖、>> 追加;2> 只重定向错误;> file 2>&1 或 &> file 都进同一文件;/dev/null 丢弃。
- 输入重定向:< 文件;<< 标记 here 文档;<<< 字符串 here 字符串。
- 管道:| 连接标准输出到下一命令标准输入;要让错误也进管道先 2>&1。
- tee:同时写文件和继续管道;顺序注意 2>&1 在 > file 之后。
多练:把命令结果写文件、追加日志、丢错误、从文件读、here 文档、管道加 grep,再结合 Shell变量.md 里的命令替换 $( ),输入输出重定向就能用熟。
文档以 Bash 为准;部分写法在 sh 或其它 Shell 中可能略有差异(如 &>、<<<)。