Skip to content

输入输出重定向

概述

在 Linux 中,每个进程都有三个标准数据流:标准输入、标准输出和标准错误。重定向允许我们改变这些数据流的来源和目的地。

标准数据流

                    ┌─────────────────┐
   标准输入 (stdin) │                 │ 标准输出 (stdout)
   文件描述符: 0    │                 │ 文件描述符: 1
   ─────────────────►     进程        ├─────────────────►
                    │                 │
                    │                 │ 标准错误 (stderr)
                    │                 │ 文件描述符: 2
                    └────────┬────────┘

文件描述符默认设备说明
stdin0键盘程序的输入
stdout1屏幕程序的正常输出
stderr2屏幕程序的错误输出

输出重定向

重定向到文件(覆盖)

使用 > 将标准输出重定向到文件,会覆盖文件内容:

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.txt

Here 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.txt

noclobber 选项

防止意外覆盖文件:

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 简介

下一章:管道与过滤器