1. 追加内容之前,先搞懂重定向的本质
在 Linux 下混了这么多年,我发现很多人对“往文件里追加内容”这件事的理解,都停留在“用 >> 就行”的层面。但真要上手写脚本、做日志、改配置的时候,各种问题就冒出来了:为什么追加完内容跑到文件开头了?为什么追加不了但也没报错?为什么加了 sudo 还是 permission denied?
这些问题,追根溯源还是没搞懂 Shell 重定向的本质。我在实际带人的时候常说一句话:>> 只是表象,文件描述符和重定向机制才是内功。内功不扎实,招式再多也会翻车。
1.1 为什么 > 和 >> 看起来一样,结果却天差地别
先看一个最简单的对比:
bash复制echo "hello" > test.txt
echo "world" >> test.txt
第一条命令用 > 把 hello 写入 test.txt,注意,这一步如果 test.txt 之前有内容,会被直接清空重写。第二条命令用 >> 把 world 追加到末尾,原有内容不受影响。
很多新手记不住这个区别,我通常给一个生活化的类比:> 是“新建文档并输入”,你每按一次回车,之前写的东西就没了,因为整个文档被覆盖了;>> 是“打开文档拉到末尾继续写”,之前的记录全都保留。
但这个类比只是帮助你记忆,真正的底层机制是:
>等价于在系统调用层面使用O_WRONLY | O_CREAT | O_TRUNC打开文件。O_TRUNC是关键,它会把文件长度截断为 0。>>等价于使用O_WRONLY | O_CREAT | O_APPEND打开文件。O_APPEND意味着每次写入前,内核都会把文件偏移量定位到末尾,而且是原子操作。
这里有个很多人忽略的细节:O_APPEND 的“定位到末尾”是内核保证的原子操作,不是 Shell 先调用 lseek 再调用 write 那么简单。这就解释了为什么多进程并发追加日志时,>> 通常不会互相覆盖(后面我会专门展开讨论这个问题)。
1.2 文件描述符才是理解输出的关键
在 Bash 中,每个进程默认有 3 个标准的文件描述符:
| 文件描述符 | 名称 | 默认指向 | 常见用途 |
|---|---|---|---|
| 0 | stdin | 键盘输入 | 读取输入 |
| 1 | stdout | 终端屏幕 | 正常输出 |
| 2 | stderr | 终端屏幕 | 错误输出 |
当我们执行 echo "msg" >> file,本质上是:
- Shell 以追加模式打开 file,得到一个新的文件描述符(比如 3)。
- Shell 临时把 fd 1(stdout)重定向到 fd 3。
- echo 进程运行时,它向 stdout 写入的内容,实际上都进了 file。
- 命令执行完毕,Shell 恢复 fd 1 的指向。
理解了这一点,你就能看明白很多“奇怪”的写法:
bash复制# 把正常输出和错误输出都追加到同一个文件
command >> log.txt 2>&1
# 把错误输出追加到 err.log,正常输出追加到 out.log
command >> out.log 2>> err.log
# 只丢弃正常输出,保留错误输出追加到日志
command 2>> err.log > /dev/null
2>&1 这个写法很多人背下来了,但不懂为什么必须写在后面。它表示:把 fd 2 重定向到 fd 1 当前指向的位置。如果写成 2>&1 >> log.txt,那么 fd 2 指向的是旧的 stdout(终端),而不是 log.txt,顺序错一个,行为就完全变了。实际工作中我见过不少因为这个顺序问题导致日志丢失的脚本,排查到最后就是这一行的事。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 最常用的几种追加写法与适用场景
搞懂原理之后,接下来看看实际操作中我们会用到的几种追加方式。每种方式都有它适合的场景,选对了能省不少事。
2.1 单行追加:echo 和 printf 的取舍
最基础的需求是追加一行文本,大家第一个想到的肯定是 echo:
bash复制echo "2026-05-01 10:00:00 INFO 服务启动成功" >> app.log
echo 的好处是简单直观,默认会在末尾补一个换行符,保证每次追加都独占一行。不过有两个小坑需要注意:
第一个坑是转义字符。在默认情况下(不开启 -e 参数),echo 不会解释 \n、\t 这类转义序列。如果写成:
bash复制echo "第一行\n第二行" >> file.txt
你得到的是一行字面量 第一行\n第二行,而不是两行。想要换行效果,要么拆成两个 echo,要么用 echo -e:
bash复制ec
