tail 命令详解与参数实测(Ubuntu 22.04)

tail 全参数实测:默认末尾 10 行、-n N 与 -n +K、-c N、-f 实时跟踪、-F 与 -f 的日志轮转差异实测、-s 轮询间隔、--pid,Ubuntu 22.04 coreutils 8.32 全部回显实测。

Linux命令Linux系统UbuntuUbuntu 22.04命令行文本处理2026/10/15

tail 命令详解与参数实测(Ubuntu 22.04)

目标:全面掌握 tail 的取尾与跟踪能力——默认末尾 10 行、-n N 与 -n +K「从第 K 行到末尾」、-c N 按字节截取、-f 实时跟踪追加、-F 在日志轮转(mv 走原文件再新建同名文件)后继续跟踪的实测差异、-s 轮询间隔,全部命令与回显已在 Ubuntu 22.04(tail 属 GNU coreutils 8.32)实测通过。

一、环境信息

本节记录实测环境与演示数据,便于读者对照复现。tail 与 head 参数对称、方向相反,但 tail 多出一组独门能力——-f/-F 文件跟踪,这是看日志的核心工具。环境信息里同时列出演示目录与测试文件。

  • 操作系统:Ubuntu 22.04.5 LTS(x86_64)
  • tail 版本:GNU coreutils 8.32(软件包 coreutils 8.32-4.1ubuntu1.4,见 2.3 实测)
  • 演示目录:/root/demo/tail/
  • 测试数据:30 行长文本 long.txt、5 行短文件 a.txt 与 b.txt(多文件演示)、live.log(-f 跟踪)、rotate.log 与 plain.log(-F/-f 轮转对照)、poll.log(-s 演示)

二、命令概览

2.1 语法与测试数据准备

帮助原文给出的语法是 tail [OPTION]... [FILE]...,职责一句话:Print the last 10 lines of each FILE to standard output(输出每个文件的最后 10 行)。参数体系分三层:取数层(-n 行 / -c 字节,均支持 +NUM 从头定位)、跟踪层(-f/-F 实时跟随文件增长)、节奏层(-s 轮询间隔、--pid 联动退出)。

准备演示数据——30 行长文本、两个 5 行短文件、若干日志样例:

printf "tail demo line %02d\n" $(seq 1 30) > /root/demo/tail/long.txt
printf "alpha-1\nalpha-2\nalpha-3\nalpha-4\nalpha-5\n" > /root/demo/tail/a.txt
printf "bravo-1\nbravo-2\nbravo-3\nbravo-4\nbravo-5\n" > /root/demo/tail/b.txt

验证数据就绪——long.txt 30 行、a.txt/b.txt 各 5 行:

$ wc -l /root/demo/tail/long.txt /root/demo/tail/a.txt /root/demo/tail/b.txt
 30 /root/demo/tail/long.txt
  5 /root/demo/tail/a.txt
  5 /root/demo/tail/b.txt
 40 total

2.2 --help 全量回显

tail 的帮助较长,除参数表外还有一段 -f 默认行为的官方说明(正是 4.3 实测的主题),原文完整收录:

Usage: tail [OPTION]... [FILE]...
Print the last 10 lines of each FILE to standard output.
With more than one FILE, precede each with a header giving the file name.

With no FILE, or when FILE is -, read standard input.

Mandatory arguments to long options are mandatory for short options too.
  -c, --bytes=[+]NUM       output the last NUM bytes; or use -c +NUM to
                             output starting with byte NUM of each file
  -f, --follow[={name|descriptor}]
                           output appended data as the file grows;
                             an absent option argument means 'descriptor'
  -F                       same as --follow=name --retry
  -n, --lines=[+]NUM       output the last NUM lines, instead of the last 10;
                             or use -n +NUM to output starting with line NUM
      --max-unchanged-stats=N
                           with --follow=name, reopen a FILE which has not
                             changed size after N (default 5) iterations
                             to see if it has been unlinked or renamed
                             (this is the usual case of rotated log files);
                             with inotify, this option is rarely useful
      --pid=PID            with -f, terminate after process ID, PID dies
  -q, --quiet, --silent    never output headers giving file names
      --retry              keep trying to open a file if it is inaccessible
  -s, --sleep-interval=N   with -f, sleep for approximately N seconds
                             (default 1.0) between iterations;
                             with inotify and --pid=P, check process P at
                             least once every N seconds
  -v, --verbose            always output headers giving file names
  -z, --zero-terminated    line delimiter is NUL, not newline
      --help     display this help and exit
      --version  output version information and exit

NUM may have a multiplier suffix:
b 512, kB 1000, K 1024, MB 1000*1000, M 1024*1024,
GB 1000*1000*1000, G 1024*1024*1024, and so on for T, P, E, Z, Y.
Binary prefixes can be used, too: KiB=K, MiB=M, and so on.

With --follow (-f), tail defaults to following the file descriptor, which
means that even if a tail'ed file is renamed, tail will continue to track
its end.  This default behavior is not desirable when you really want to
track the actual name of the file, not the file descriptor (e.g., log
rotation).  Use --follow=name in that case.  That causes tail to track the
named file in a way that accommodates renaming, removal and creation.

GNU coreutils online help: <https://www.gnu.org/software/coreutils/>
Report any translation bugs to <https://translationproject.org/team/>
Full documentation <https://www.gnu.org/software/coreutils/tail>
or available locally via: info '(coreutils) tail invocation'

💡 说明:帮助末尾那段官方说明是本文 4.3 实测的「标准答案」——-f 默认跟随文件描述符(文件被改名仍跟旧 inode),-F 等价 --follow=name --retry,跟随文件名、适应改名/删除/重建。先读懂这段,再看实测对照。

2.3 版本确认

确认 tail 的归属与版本:

$ tail --version | head -1
tail (GNU coreutils) 8.32

输出 tail (GNU coreutils) 8.32,即本文全部回显均来自 coreutils 8.32 的实现。

三、参数逐项实测

3.1 无参数:默认输出末尾 10 行

不带任何参数,tail 输出文件最后 10 行。对 30 行的 long.txt 执行:

$ tail /root/demo/tail/long.txt
tail demo line 21
tail demo line 22
tail demo line 23
tail demo line 24
tail demo line 25
tail demo line 26
tail demo line 27
tail demo line 28
tail demo line 29
tail demo line 30

只输出第 21~30 行,保持原有顺序(这一点与 tac 的倒序不同)。默认 10 与 head 对称,同样来自 POSIX 约定。

💡 提示:看日志最新状态的第一动作就是 tail 文件名;配合 sudo tail /var/log/syslog 这类只读探查,不会改动文件。

3.2 -n N:指定行数

-n N 把默认 10 改成任意行数。取末尾 3 行:

$ tail -n 3 /root/demo/tail/long.txt
tail demo line 28
tail demo line 29
tail demo line 30

取末尾 12 行(文件只剩 30 行,输出仍保持原序):

$ tail -n 12 /root/demo/tail/long.txt
tail demo line 19
tail demo line 20
tail demo line 21
tail demo line 22
tail demo line 23
tail demo line 24
tail demo line 25
tail demo line 26
tail demo line 27
tail demo line 28
tail demo line 29
tail demo line 30

帮助原文里 -n 的完整形式是 --lines=[+]NUM——方括号里的加号是 3.3 要实测的「从第 K 行开始」用法。

💡 提示:tail -n 1 单取最后一行是脚本高频写法;与 head -n N 对称记忆:head 从头数、tail 从尾数。

3.3 -n +K:从第 K 行输出到末尾

-n 后给加号时语义反转:从第 K 行开始输出到末尾。tail -n +28 即从第 28 行输出到第 30 行:

$ tail -n +28 /root/demo/tail/long.txt
tail demo line 28
tail demo line 29
tail demo line 30

tail -n +1 从第 1 行到末尾,等价于整个文件(配合 wc 验证行数):

$ tail -n +1 /root/demo/tail/long.txt | wc -l
30

输出 30,全量 30 行。+K 形式的价值是「跳过头部固定行数」:CSV 前 1 行表头用 tail -n +2 剥离,比 head -n -N(需知道总行数)通用。

💡 提示:-n +1 文件 与 cat 文件 输出等价,但它走的是 tail 的「定位后输出」路径,某些「跳过文件开头大段内容」的场景(如 tail -c +K 跳过字节)只有这套 +NUM 语法能做到。

3.4 -c N:按字节截取

-c N 按字节取尾部,同样可能出现「半行」。取末尾 25 字节(tail demo line 30\n 共 19 字节,倒数第二行只够取尾部 6 字节):

$ tail -c 25 /root/demo/tail/long.txt
ine 29
tail demo line 30

第一行输出 ine 29——正是 tail demo line 29 被从中间切掉的结果。-c 也支持 +NUM 从头定位与倍数后缀,取末尾 100 字节并统计实际字节数:

$ tail -c 100 /root/demo/tail/long.txt | wc -c
100

输出 100,精确按字节交付。

💡 提示:帮助原文注明的倍数后缀同样适用于 tail:tail -c 1M 文件 取最后 1MiB,是快速抽查超大文件尾部的标准姿势。

3.5 旧式 -5 写法

与 head 对称的历史写法:-N 等价于 -n N。tail -5:

$ tail -5 /root/demo/tail/long.txt
tail demo line 26
tail demo line 27
tail demo line 28
tail demo line 29
tail demo line 30

输出末尾 5 行,与 tail -n 5 完全一致。老脚本里 tail -1(单取最后一行)随处可见;新脚本建议统一 -n N,避免与 -n +K 的加号语义混淆。

💡 提示:tail -1 与 tail -n 1 等价,但 tail +2(旧式「从第 2 行开始」)在新版 coreutils 里已失效——新语法必须写 tail -n +2,迁移老脚本时逐一检查。

四、实战示例

4.1 多文件输出与 ==> 文件 <== 头

tail 跟多个文件时打印 ==> 文件名 <== 分隔头(与 head 同款约定)。两个文件各取末尾 2 行:

$ tail -n 2 /root/demo/tail/a.txt /root/demo/tail/b.txt
==> /root/demo/tail/a.txt <==
alpha-4
alpha-5

==> /root/demo/tail/b.txt <==
bravo-4
bravo-5

分隔头让多文件输出各归各位。-q 关闭头、-v 在单文件时强制打开,均见 2.2 帮助原文。

💡 提示:同时盯多个服务的日志可用 tail -n 5 app.log err.log,分隔头标明每段来自哪个文件;解析脚本里则加 -q。

4.2 -f:实时跟踪文件增长

-f(--follow)让 tail 不退出,持续输出新追加的内容。先造一个有两行的日志文件并确认内容:

$ echo "boot line 1" > /root/demo/tail/live.log
$ echo "boot line 2" >> /root/demo/tail/live.log
$ cat /root/demo/tail/live.log
boot line 1
boot line 2

启动 tail -f 并在其运行期间追加两行(后台子 shell 负责写入,timeout 4 秒后结束跟踪):

$ (sleep 1; echo "appended at t+1s" >> /root/demo/tail/live.log; sleep 1.5; echo "appended at t+2s" >> /root/demo/tail/live.log) & timeout 4 tail -f /root/demo/tail/live.log
boot line 1
boot line 2
appended at t+1s
appended at t+2s

tail 先输出文件现有末尾 10 行(此处 2 行),随后两条新追加的 appended at … 实时出现——-f 正是这样「尾部先打存量、新增即打增量」。实际使用时用 Ctrl+C 结束跟踪。

💡 技术拓展:「跟踪文件变化」的实现路线不止一条,常见对比如下——

方式原理/特点适用场景
tail -f默认跟随文件描述符(inotify 感知增长)文件只追加不轮转的普通日志
tail -F跟随文件名,改名/删除/重建都能接上日志会轮转(logrotate)的场景
less +F 文件进入 less 的跟踪模式,Ctrl+C 后可回看搜索边跟踪边需要翻页搜索
watch -n 1 'tail -5 文件'周期性重跑命令刷新整屏只关注末几行的面板式观察

本文实测以 tail 的 -f/-F 为主线——最轻量、可脚本化(-s 控制节奏、--pid 控制退出);需要交互式搜索跟踪时选 less +F。

4.3 -F 与 -f 的轮转差异(mv 走原文件再重建实测)

日志轮转(logrotate)的典型动作是「把当前文件 mv 走,再新建同名文件」。帮助原文已言明:-f 跟随文件描述符(旧 inode),-F 跟随文件名。用同一套操作分别实测。

先看 -F:造一个两行的 rotate.log,启动 timeout 9 tail -F,期间把原文件 mv 成 .old,再向新建的同名文件追加两行:

$ rm -f /root/demo/tail/rotate.log*; echo "gen-A line 1" >> /root/demo/tail/rotate.log; echo "gen-A line 2" >> /root/demo/tail/rotate.log; cat /root/demo/tail/rotate.log
gen-A line 1
gen-A line 2

$ (sleep 1; mv /root/demo/tail/rotate.log /root/demo/tail/rotate.log.old; sleep 2; echo "gen-B line 1" >> /root/demo/tail/rotate.log; sleep 3; echo "gen-B line 2" >> /root/demo/tail/rotate.log) & timeout 9 tail -F /root/demo/tail/rotate.log
gen-A line 1
gen-A line 2
tail: '/root/demo/tail/rotate.log' has been replaced;  following new file
gen-B line 1
gen-B line 2

关键两处:其一,tail 检测到文件被替换,打印 tail: '…rotate.log' has been replaced; following new file;其二,新文件里的 gen-B line 1/2 被继续跟踪输出——-F 全程无感切换,这正是生产环境看轮转日志的标准选择。

再看 -f 对照:同样的 mv + 新建 + 追加,timeout 4 tail -f 全程只输出旧内容:

$ rm -f /root/demo/tail/plain.log*; echo "plain 1" >> /root/demo/tail/plain.log; echo "plain 2" >> /root/demo/tail/plain.log; cat /root/demo/tail/plain.log
plain 1
plain 2

$ (sleep 1; mv /root/demo/tail/plain.log /root/demo/tail/plain.log.old; sleep 1; echo "plain 3 into NEW file" >> /root/demo/tail/plain.log) & timeout 4 tail -f /root/demo/tail/plain.log
plain 1
plain 2

新文件里的 plain 3 into NEW file 没有出现——-f 还抱着旧 inode(已改名为 plain.log.old)继续等待,而旧文件再无写入。这就是帮助原文所说「跟踪描述符而非文件名」的实际后果。

💡 提示:一句话选型——文件只增不轮转用 -f;任何接了 logrotate 的日志一律 -F(它同时兼容不轮转的情况,代价可忽略)。

4.4 -s:控制跟踪轮询间隔

-s N(--sleep-interval)调整 -f 每次检查间隔(默认 1.0 秒)。以 2 秒间隔跟踪,期间追加两行:

$ echo "poll seed" > /root/demo/tail/poll.log
$ (sleep 1; echo "poll tick 1" >> /root/demo/tail/poll.log; sleep 1.5; echo "poll tick 2" >> /root/demo/tail/poll.log) & timeout 5 tail -s 2 -f /root/demo/tail/poll.log
poll seed
poll tick 1
poll tick 2

两条追加内容都被捕获。-s 不改变输出内容,只改变检查节奏:调大(如 -s 5)降低 CPU/IO 开销适合超低频日志,调小(如 -s 0.1)提高实时性。帮助原文还说明:与 --pid 组合时,-s 也决定检查目标进程存活的最小周期。

💡 提示:--pid=PID 是脚本化的「自动收尾」开关——tail -f 日志 --pid=$目标进程号 会在目标进程退出后自动结束跟踪,常用于「跟随某服务启动日志直到它就绪」的部署脚本,无需手工 Ctrl+C 或 timeout。

五、注意事项与常见问题

Q1:tail -f 挂着不动,会不会一直占着资源? 会常驻一个进程但开销极小:tail 用 inotify 被动感知文件变化,无写入时几乎不消耗 CPU(帮助原文注明 -s 默认 1 秒间隔用于轮询兜底)。用完记得 Ctrl+C;脚本里用 --pid 或 timeout 收尾。

Q2:日志轮转后 tail -f 突然「没有新内容了」,为什么? -f 绑定的是旧文件描述符,轮转(mv/重建)后新文件与它无关(见 4.3 的 -f 对照实测:plain 3 未出现)。改用 tail -F,实测中它打印了 has been replaced; following new file 并接上了新文件。

Q3:tail -n +2 和 tail -2 有什么区别? 完全不同的两件事:-n +2 从第 2 行输出到末尾(跳过表头),-2 是旧式写法、等价 -n 2(只取末尾 2 行)。正负号语义:+K 从头定位、-K/无符号从尾取。

Q4:tail -c 25 的第一行怎么是半截单词? 字节截取不认行边界(见 3.4 实测 ine 29),文本场景建议 -n;-c 的主场是大文件的尾部抽查与二进制尾部检查。

Q5:tail 多文件时默认行数怎么控制? -n N/-c N 对每个文件分别生效,配合 ==> 文件 <== 分隔头(见 4.1);加 -q 可去掉分隔头。

Q6:tail -f 能跟踪还不存在的文件吗? -f 默认不行,文件不存在直接报错退出;-F(含 --retry 语义)会持续重试直到文件出现,适合「等服务启动后日志才生成」的场景——这正是 -F 帮助语义 --follow=name --retry 的另一层含义。

六、参考链接