输入输出重定向
概述
在 Linux 中,每个进程都有三个标准数据流:标准输入、标准输出和标准错误。重定向允许我们改变这些数据流的来源和目的地。
标准数据流
┌─────────────────┐
标准输入 (stdin) │ │ 标准输出 (stdout)
文件描述符: 0 │ │ 文件描述符: 1
─────────────────► 进程 ├─────────────────►
│ │
│ │ 标准错误 (stderr)
│ │ 文件描述符: 2
└────────┬────────┘
│
▼| 流 | 文件描述符 | 默认设备 | 说明 |
|---|---|---|---|
| stdin | 0 | 键盘 | 程序的输入 |
| stdout | 1 | 屏幕 | 程序的正常输出 |
| stderr | 2 | 屏幕 | 程序的错误输出 |
输出重定向
重定向到文件(覆盖)
使用 > 将标准输出重定向到文件,会覆盖文件内容:
bash
# 将命令输出写入文件
$ echo "Hello, World!" > output.txt
# 查看结果
$ cat output.txt
Hello, World!
# 再次写入会覆盖
$ echo "New content" > output.txt
$ cat output.txt
New content
# 将命令输出保存到文件
$ ls -la > filelist.txt
$ date > current_date.txt追加到文件
使用 >> 将输出追加到文件末尾:
bash
# 追加内容
$ echo "Line 1" > file.txt
$ echo "Line 2" >> file.txt
$ echo "Line 3" >> file.txt
$ cat file.txt
Line 1
Line 2
Line 3
# 追加日志
$ date >> log.txt
$ echo "Task completed" >> log.txt重定向标准错误
使用 2> 重定向标准错误:
bash
# 将错误信息写入文件
$ ls /nonexistent 2> error.txt
$ cat error.txt
ls: cannot access '/nonexistent': No such file or directory
# 追加错误信息
$ ls /another_nonexistent 2>> error.txt同时重定向输出和错误
bash
# 方法 1:分别重定向
$ command > output.txt 2> error.txt
# 方法 2:合并到同一文件
$ command > all.txt 2>&1
# 方法 3:简写形式(Bash 4+)
$ command &> all.txt
# 方法 4:追加模式
$ command >> all.txt 2>&1
$ command &>> all.txt丢弃输出
将输出重定向到 /dev/null:
bash
# 丢弃标准输出
$ command > /dev/null
# 丢弃标准错误
$ command 2> /dev/null
# 丢弃所有输出
$ command > /dev/null 2>&1
$ command &> /dev/null重定向顺序
重定向的顺序很重要:
bash
# 正确:先重定向 stdout,再将 stderr 指向 stdout
$ command > file.txt 2>&1
# 错误:顺序不对,stderr 还是指向屏幕
$ command 2>&1 > file.txt输入重定向
从文件读取输入
使用 < 从文件读取输入:
bash
# 从文件读取
$ wc -l < file.txt
10
# 排序文件内容
$ sort < unsorted.txt
# 与输出重定向组合
$ sort < unsorted.txt > sorted.txtHere Document(Here Doc)
使用 << 提供多行输入:
bash
# 基本语法
$ cat << EOF
Line 1
Line 2
Line 3
EOF
# 写入文件
$ cat << EOF > file.txt
This is line 1
This is line 2
This is line 3
EOF
# 传递给命令
$ mysql -u root << EOF
USE database;
SELECT * FROM table;
EOF终止符说明
EOF是自定义的终止符,可以是任意字符串- 终止符必须单独成行
- 终止符前后不能有空格(除非使用
<<-)
Here String
使用 <<< 提供单行字符串输入:
bash
# 基本语法
$ cat <<< "Hello, World!"
Hello, World!
# 传递变量
$ name="Alice"
$ cat <<< "Hello, $name!"
Hello, Alice!
# 作为命令输入
$ bc <<< "2 + 3"
5
$ grep "pattern" <<< "This line contains pattern"
This line contains pattern文件描述符
理解文件描述符
bash
# 0 = stdin(标准输入)
# 1 = stdout(标准输出)
# 2 = stderr(标准错误)
# 3-9 = 可用于自定义
# 查看进程的文件描述符
$ ls -l /proc/$$/fd/
lrwx------ 1 user user 64 Jan 1 10:00 0 -> /dev/pts/0
lrwx------ 1 user user 64 Jan 1 10:00 1 -> /dev/pts/0
lrwx------ 1 user user 64 Jan 1 10:00 2 -> /dev/pts/0使用自定义文件描述符
bash
# 打开文件描述符 3 用于写入
$ exec 3> output.txt
$ echo "Hello" >&3
$ echo "World" >&3
$ exec 3>&- # 关闭文件描述符
# 打开文件描述符 4 用于读取
$ exec 4< input.txt
$ read line <&4
$ echo $line
$ exec 4<&- # 关闭文件描述符
# 同时读写
$ exec 3<> file.txt复制文件描述符
bash
# 将 fd 3 指向 fd 1
$ exec 3>&1
# 将 fd 4 指向 fd 0
$ exec 4<&0
# 将 stderr 指向 stdout
$ command 2>&1实用示例
日志记录
bash
#!/bin/bash
# 记录脚本执行日志
LOGFILE="script.log"
# 重定向所有输出到日志文件
exec > >(tee -a "$LOGFILE") 2>&1
echo "脚本开始执行: $(date)"
# ... 脚本内容 ...
echo "脚本执行完成: $(date)"分离输出和错误
bash
# 正常输出到一个文件,错误到另一个文件
$ find / -name "*.conf" > found.txt 2> errors.txt
# 只看正常输出,忽略错误
$ find / -name "*.conf" 2> /dev/null同时显示和保存输出
使用 tee 命令:
bash
# 显示并保存到文件
$ ls -la | tee filelist.txt
# 追加模式
$ ls -la | tee -a filelist.txt
# 同时保存到多个文件
$ ls -la | tee file1.txt file2.txt
# 包括 stderr
$ command 2>&1 | tee output.txt输入输出组合
bash
# 读取文件,处理后输出到另一个文件
$ sort < unsorted.txt > sorted.txt
# 多个输入
$ cat < file1.txt < file2.txt # 只读取最后一个
# 正确方式
$ cat file1.txt file2.txt > combined.txt创建空文件或清空文件
bash
# 创建空文件
$ > newfile.txt
# 清空文件
$ > existingfile.txt
# 使用 : 命令
$ : > file.txt追加时间戳日志
bash
# 每行添加时间戳
$ command | while read line; do
echo "$(date '+%Y-%m-%d %H:%M:%S') $line"
done >> logfile.txtnoclobber 选项
防止意外覆盖文件:
bash
# 启用 noclobber
$ set -o noclobber
# 现在 > 不能覆盖已存在的文件
$ echo "test" > existingfile.txt
bash: existingfile.txt: cannot overwrite existing file
# 强制覆盖(使用 >|)
$ echo "test" >| existingfile.txt
# 关闭 noclobber
$ set +o noclobber高级重定向
交换 stdout 和 stderr
bash
# 交换 stdout 和 stderr
$ command 3>&1 1>&2 2>&3
# 实际应用:将 stderr 管道给 grep
$ command 3>&1 1>&2 2>&3 | grep "error"进程替换
使用 <() 和 >():
bash
# 将命令输出作为文件
$ diff <(ls dir1) <(ls dir2)
# 将输入分发到多个命令
$ echo "hello" | tee >(cat -n) >(wc -c)
# 比较两个命令的输出
$ comm <(sort file1.txt) <(sort file2.txt)读取到变量
bash
# 将命令输出读入变量
$ result=$(command)
# 读取文件到变量
$ content=$(< file.txt)
# 读取第一行
$ read first_line < file.txt常见错误和陷阱
陷阱 1:输入输出使用同一文件
bash
# 错误:会清空文件!
$ sort < file.txt > file.txt
# 正确方式
$ sort file.txt > temp.txt && mv temp.txt file.txt
# 或使用 sponge(需要安装 moreutils)
$ sort file.txt | sponge file.txt陷阱 2:管道中的变量
bash
# 变量在子 Shell 中修改,不影响父 Shell
$ count=0
$ cat file.txt | while read line; do
((count++))
done
$ echo $count # 仍然是 0
# 正确方式
$ count=0
$ while read line; do
((count++))
done < file.txt
$ echo $count陷阱 3:重定向顺序
bash
# 错误顺序
$ command 2>&1 > file.txt
# stderr 仍然输出到终端
# 正确顺序
$ command > file.txt 2>&1
# stdout 和 stderr 都写入文件小结
本章介绍了 Linux 的输入输出重定向:
- 输出重定向:
>(覆盖)、>>(追加) - 错误重定向:
2>、2>> - 合并重定向:
&>、2>&1 - 输入重定向:
<、<<(Here Doc)、<<<(Here String) - 文件描述符:0(stdin)、1(stdout)、2(stderr)
- 实用工具:
tee、/dev/null
掌握重定向是使用 Linux 命令行的重要技能,它让你能够灵活地处理数据流和自动化任务。
上一章:Shell 简介
下一章:管道与过滤器