tail -f 盯着日志排查问题,半夜日志明明还在滚,屏幕却突然静止不动了——日志没停,是 tail -f 被日志轮转甩掉了。这是这对”最简单的命令”里藏得最深的坑,放在最后讲;先把 head 和 tail 本身理顺。

这是 Linux 命令系列的一篇,承接 Linux 常见命令与工具地图head 输出文件(或管道输入)的开头部分,tail 输出结尾部分,默认都是 10 行——两个天生一对的小工具,和 cut/sort/uniq 那批 一样,真正的威力在管道组合里。

head:取开头

1
2
3
4
5
6
7
8
9
10
11
12
13
head /var/log/app.log
# 默认输出文件前 10 行

head -n 50 /var/log/app.log
# -n:指定行数,这里取前 50 行

head -c 100 data.bin
# -c:按字节数取而不是按行,这里取前 100 个字节
# 检查一个大文件/二进制文件的开头是什么内容时有用

ls -t | head -n 5
# 管道用法:ls -t 按修改时间排序,head 取最新的 5 个文件名
# "取排序结果的前 N 个"是 head 在管道里最常见的角色

tail:取结尾,和一个特殊语法

1
2
3
4
5
6
7
8
9
tail /var/log/app.log
# 默认输出文件最后 10 行

tail -n 200 /var/log/app.log
# 最后 200 行——排查刚发生的问题,第一反应通常就是这条

tail -n +5 data.csv
# 注意 +5 带加号,语义完全不同:从第 5 行开始一直输出到文件末尾
# 最常见的用途是跳过文件开头的表头/说明行(+2 就是跳过第一行表头)

-n +N 这个加号语法容易被忽略:tail -n 5 是”最后 5 行”,tail -n +5 是”从第 5 行到结尾”——一个从后往前数,一个从前往后跳,方向完全相反。

head + tail 组合:取中间任意一段

两个命令一接,就能精确取出文件中间的任意行段:

1
2
3
head -n 12 access.log | tail -n 5
# 先取前 12 行,再从这 12 行里取最后 5 行——最终得到第 8 到第 12 行
# 公式:取第 M 到第 N 行 = head -n N | tail -n $((N-M+1))

典型场景:报错信息在日志第 3800 行附近,head -n 3820 app.log | tail -n 40 直接把上下文捞出来,不用打开几十万行的大文件翻。

tail -f:实时跟踪日志

1
2
3
4
5
6
7
8
9
10
11
tail -f /var/log/app.log
# -f:follow,输出最后 10 行后不退出,持续等待并输出新追加的内容
# 盯着服务日志看请求进来的样子,就是它

tail -f /var/log/app.log | grep --line-buffered ERROR
# 配合 grep 只看错误行
# --line-buffered:让 grep 逐行刷新输出——管道里 grep 默认会攒缓冲,
# 不加这个参数会感觉"日志卡住了很久突然涌出一批"

tail -f access.log error.log
# 同时跟踪多个文件,输出里会标注每段来自哪个文件

tail -F:日志轮转的正解

回到开头的问题。tail -f 打开文件后,盯住的是打开那一刻的文件本体(准确说是它的 inode,这个概念在 ln 那篇 讲过)。而日志轮转(logrotate)的动作通常是:把 app.log 改名成 app.log.1,再新建一个空的 app.log 继续写。

于是 -f 的困境出现了:它还忠实地盯着旧 inode——也就是改名后的 app.log.1,那个文件再也不会有新内容了,屏幕自然一片死寂;新日志全写进了新建的 app.log,没人看。

-F 就是为这个场景设计的:

1
2
3
4
tail -F /var/log/app.log
# -F:在 -f 的基础上,持续检查文件名对应的 inode 有没有变化
# 检测到轮转(inode 变了)就关掉旧文件、重新打开新文件,无缝接上
# 文件暂时不存在也不报错退出,而是等它出现——服务重建日志文件的间隙也能扛过去

结论很简单:只要盯的日志会轮转(生产环境的日志几乎都会),一律用大写 F。多敲一个大写字母,省掉半夜”日志为什么不动了”的惊魂。

常用参数速查表

命令/参数 作用
head 文件 / tail 文件 取开头 / 结尾,默认各 10 行
head -n N / tail -n N 指定行数
head -c N 按字节取开头 N 字节
tail -n +N 从第 N 行取到结尾(+2 跳过表头)
head -n N | tail -n K 取第 N-K+1 到第 N 行的中间段
tail -f 实时跟踪新增内容
tail -f | grep --line-buffered 实时过滤,避免 grep 缓冲攒批
tail -F 跟踪且自动处理日志轮转,生产日志一律用它

写到这里

head/tail 五分钟就能全部学完,值得刻进记忆的是三个点:-n +N 的加号语义(从第 N 行到结尾)、head | tail 取中间段的组合、以及 -F-f 的完全上位替代(只要日志会轮转)。最后这个坑踩过一次就再也忘不掉——不如直接从今天开始把 -F 设成肌肉记忆。