
1. 一个被低估的命令从日志排查说起1.1 那天晚上我靠head救了场月初线上服务出了点状况。配置中心在凌晨悄悄推送了一批新参数凌晨四点多某个节点就开始反复重启。等我们拿到现场时问题日志已经被日志轮转切成好几个文件入口线索只藏在最早那一段里。当时的情况是有一个12GB的app.log.1问题生命周期集中在前几百行而我需要把那些行单独捞出来。身边同事第一反应是vim硬拉文件结果光标一进去就卡住内存蹭蹭涨。我用两条命令解决了head -n 500 app.log.1如果线索范围更靠中间就配合tail精确切段tail -n 200 app.log.1 | head -n 300意思是从第200行开始往后取300行。整个排查过程不到一分钟线索文件已经躺在/tmp/range.log里了。我习惯性地说了句“用head切一下就出来了”新同事却问我head不就是看文件开头几行吗有什么好研究的这个问题我认真想过。head确实简单但它是Linux文本处理体系里最基础的积木。简单不等于没东西可挖恰恰是这种每天都会敲的命令最容易出现“会用不会讲、会用不会组合”的情况。网上那些“Linux常用命令大全”里head永远是标配但很少有人说清楚它背后的参数细节、管道行为、性能特性以及那些真实场景里会踩的坑。这篇围绕head这个命令完整梳理一遍适合刚碰Linux的入门者也适合天天跟日志、CSV、配置文件打交道的运维和开发。看完之后你会发现head不仅是个“节流阀”还是一个可以安全组合、精确切片的文本利器。1.2 head的本体是什么head是GNU coreutils的成员几乎所有Linux发行版默认自带不依赖外部包。它的核心功能是从文件开头按行或按字节取出内容和cat、tail、sed一起构成了文本预览的四大件。它的名字很直白对应tail一个取头部一个取尾部这种“每个工具只做一件事组合起来做复杂的事”的设计正是Unix哲学的典型。但head真正的主场在管道里。很多人习惯cat一个文件然后用眼睛盯但文件一大就废了。head在管道里的作用是一个“节流阀”限制上游命令的输出量。这个用途比单独读一个文件更普遍也更重要后面专门讲。2. 参数拆解按行、按字节、多文件的细节2.1 -n 参数比你想象的多一点最基础的是-n指定行数。以下三种写法等价head -n 20 file.txt head -20 file.txt head -n20 file.txt其中-20这种省略写法是传统短选项风格我在脚本里更喜欢写完整的-n 20因为语义清楚别人读代码时不至于猜。但-n还有一个经常被忽略的形式负数。head -n -5表示输出“除了最后5行之外的全部行”。这个在实际中特别实用比如你有一个日志文件尾部有十几行的堆栈信息你不想看只想看前面的主体head -n -10 app.log.1这和tail -n 11的结果其实互补。注意我强调一下head -n -K是GNU扩展BSD版本的head比如macOS自带不一定支持。所以在跨平台脚本里我一般会先head --version或者干脆用sed规避兼容性问题。还有个冷门写法head -n 5表示从第5行输出到文件末尾但实际工作中没人这么用——要取“从某行到结尾”大家更习惯tail -n 5语义更直观。-n 0也值得一提head -n 0 file.txt cat file.txt | head -n 0这行命令不会输出任何内容但执行本身是有意义的。它常被用在脚本里“探测管道是否通”“让上游命令跑一遍”的场景。比如我想判断某个进程是否还活着、它的日志输出是否还能写就执行tail -f app.log | head -n 0head关闭管道后tail退出命令本身没有副作用但能验证连通性。2.2 -c 参数按字节精确截取-c按字节数截取不看行。这在处理二进制文件、文件头判断、精确提取指定大小数据时很关键head -c 512 file.bin比如我想快速判断一个没有扩展名的文件到底是什么直接把前几百字节丢给file命令识别head -c 512 unknown.dat | file -输出会告诉你这是ELF可执行文件、gzip压缩包、PDF、JPEG还是SQLite数据库。原理很简单file靠读取文件头特征字节来判断类型512个字节足够覆盖绝大多数魔数。-c还支持单位后缀GNU head允许写成K、M、G等head -c 1K file.bin head -c 1M file.bin head -c 1G file.bin写脚本时常用head -c 1M bigfile sample.bin来切分大文件的第一段。同样这个单位后缀也是GNU扩展跨平台时要注意。按行和按字节最大的区别是文本文件按行自然因为换行符是逻辑边界二进制文件没有“行”的概念只能按字节切。另外对UTF-8多字节文本用-c要小心可能截到半个汉字后面我会在坑点里细讲。2.3 多文件时的 -v 与 -q当一次处理多个文件时head默认会在每一份内容前加一行“ 文件名 ”的头部信息方便你区分来源head -n 3 conf.d/*.conf输出会是这样 conf.d/app.conf server { listen 80; conf.d/cache.conf proxy_cache_path /data/cache levels1:2这个头在单独处理一个文件时是不显示的。如果你想强制显示用-v如果觉得多余、想关掉用-q。我实际中经常用-q的场景是批量读取多个配置文件前几行拼成一个汇总内容再交给其他工具统计。比如head -q -n 1 conf.d/*.conf | wc -l这样直接数出所有配置文件第一行的总行数不会被文件名干扰。-v则适合快速给人看的场景不用awk也能一眼定位哪个文件是哪份。2.4 性能真相head为什么能秒开大文件很多人问过一个100GB的文件head -n 1会不会卡死答案是不会因为head是流式读取它只读到满足条件就退出。更准确地说head在读文件时用的是缓冲IO每次读取一块数据直到攒够你要求的行数或字节数然后立即关闭文件描述符、结束进程。用strace看非常直观strace -e read,openat head -n 1 bigfile.log你会发现openat打开文件后只发生了少数几次read系统调用数据量远小于文件总大小。所以head几乎不消耗内存也不耗时。但有个例外如果文件的“第一行”特别长比如一个没有换行符的巨大JSON文件head需要把这一整行读进缓冲区才能输出内存占用就会飙升。遇到这种情况用head -c限制字节数更安全head -c 4096 giant_single_line.json这一节的核心结论是head是按需消费的不是全量扫描。管道的上游是cat、grep时真正全量消耗文件的是它们不是head。理解了这一点后面所有组合和坑就都好解释了。3. 管道里的head组合拳才是本体3.1 取中间段的黄金公式很多人只会在单文件上用head遇到“取第201行到第300行”就卡住了。其实公式很简单背下来即可tail -n 201 app.log | head -n 100tail -n 201的意思是“从第201行开始输出直到结尾”然后head -n 100从这堆数据中再取前100行结果就是201到300行。整个过程是流式的两个进程并行基本没有额外内存开销。反过来先head后tail也能实现head -n 300 app.log | tail -n 100这个公式先取前300行再取最后100行同样得到201到300行。区别在于执行顺序前者从文件尾部跳转再向前切后者从文件头部推进再往回退。对于一个小文件二者没差别对于超大文件tail -n N | head -n M通常更快因为tail可以快速定位到尾部的偏移量而head只需要再读一小段。这个“黄金公式”在日志排查里出现频率极高。比如我只需要某个时间段内的日志按行号先粗切一遍tail -n 10000 app.log | head -n 5000 window.log然后对window.log做精确分析比直接扫全文件快一个量级。3.2 配合 awk 和 sed 处理表头处理CSV或TSV文件时第一行通常是字段名。head最实用的玩法是单独提取表头用来生成字段清单、转换分隔符、或者做数据字典head -n 1 data.csv | awk -F, {for(i1;iNF;i) print i, $i}这一行能输出“第几列对应什么字段名”在数据文件字段特别多的时候比用Excel打开快得多。把逗号分隔转成Tab制表符方便对齐head -n 1 data.csv | sed s/,/\t/g还有一个非常高频的需求把一个20GB的CSV文件切一个小的样本出来供本地脚本调试head -n 1000 source.csv sample.csv这样本地跑测试时不用等大文件也不用担心把整个大文件拷到开发机。我在数据导入项目里一直这么干先head -n 3看格式再切1000行做导入测试确认无误后再放开全量导入。3.3 给“失控命令”加一道保险这是head作为“节流阀”的典型用法。很多命令的输出量是你无法预料的直接打印到终端会刷屏甚至把终端卡死。在管道后面接一个head能强制限制输出量find / -name *.log 2/dev/null | head -n 20 ps aux | head -n 15 du -sh /var/* 2/dev/null | sort -rh | head -n 10其中最经典的是du管道du -sh /*可能列出几十上百个目录但你想知道的只是最大的几个于是排序后让head截断前10条。这个组合本身就是“取Top N”的标准写法。yes命令也值得提一句。很多新手试过yes结果终端疯狂滚动只能强制关闭。但只要套上head它就是无害的yes | head -n 5输出5行y后head退出yes收到管道关闭的信号也自动结束。如果你想看一个命令到底会输出多少行先用head限制一下既保护终端又保护CPU。这里有一个更进阶的替代品grep -m。比如从超大日志里找错误你只需要前5条匹配grep -m 5 ERROR app.loggrep -m 5是“匹配5次就停”它比grep ERROR app.log | head -n 5更干净不会在stderr里冒出broken pipe错误底层扫描也能提前终止。但head的通用性更强因为它不关心上游是什么命令。4. 实战地图日志、CSV、配置检查与面试题4.1 日志排查的三个黄金组合日志是head用得最多的战场没有之一。第一个组合是“看启动头部”。服务刚启动时配置文件加载、初始化参数、监听地址往往都打在日志最前面。如果服务重启过新的日志文件往往是空的或者从中间开始写这时候真正有价值的信息在旧的、被轮转走的文件头部head -n 50 app.log.1第二个组合是“看时间格式”。日志乱不乱先看第一行的时间戳。时区对不对、精度够不够一眼便知head -n 1 app.log第三个组合是“按关键字限制条数”。错误信息可能在日志里刷了十万行你只需要前面一小段来做根因分析grep -n ERROR app.log | head -n 50把-n加上保留了行号后面可以直接用“黄金公式”精确提取上下文。比如第1000行附近有异常我之前配合tail切出来的窗口范围是1000到1050行。4.2 大文件的“先尝后买”处理任何大文件之前都应该“先尝后买”。head就是试吃工具。拿到一个20GB的CSV先看列结构head -n 5 huge_table.csv看到分隔符、字段数、是否带表头再决定后续用cut、awk还是导入数据库。这个动作花不到一秒能避免后面写错解析脚本再返工。更厉害的是配合file判断文件真实类型。有一次客户发来一堆无扩展名的数据文件我挨个用这个组合分类for f in data_* ; do echo $f head -c 512 $f | file - done结果很快就分清了哪些是gzip压缩、哪些是明文JSON、哪些是SQLite数据库。file本质上就是读文件头特征手动用head切出来再喂给它效果一样但更可控。如果想看二进制魔数用xxd或odhead -c 16 archive.tar.gz | xxd看到1f8b 0800就知道这是gzip格式。处理不知道格式的二进制时这是一个很顺手的判断手段。4.3 配置检查与进程分析head在“只读前缀”的场景里很顺手。检查nginx配置的include结构head -n 20 nginx.conf系统里排查挂载顺序先看/etc/fstab前几行head -n 5 /etc/fstab处理/proc伪文件时更是离不开head。比如查看某个进程的命令行参数/proc/pid/cmdline是一个用NUL分隔的大字符串用head -c限制字节数再替换分隔符head -c 512 /proc/1234/cmdline | tr \0 echo查看/proc/pid/stat的第一行能拿到进程状态、父进程PID等关键信息也只取前一段就够了head -n 1 /proc/1234/stat/proc下的伪文件很多内容超长全部read出来只会拖慢脚本。用head限制读取量是性能上的细节习惯。4.4 面试和笔试中的head考点head在Linux面试题中的出现频率很高而且经常不是单独问而是藏在场景题里。我把常见考点整理一下题目答法考点查看文件前10行head -n 10 file基础参数查看文件第11到20行head -n 20 file | tail -n 10组合管道从第N行开始取M行tail -n N file | head -n M黄金公式head -n和head -c区别前者按行后者按字节文本vs二进制管道中head提前退出会发生什么上游进程写管道时收到SIGPIPE默认终止信号机制最后一个问题最能看出功底。如果你只说“head显示头部”面试官会追问“那head读完需要的行之后管道上游那个cat怎么办” 正确的链路是head读完第10行立即退出关闭管道读端上游进程cat继续向管道写入时内核发现读端已关闭向上游进程发送SIGPIPE信号进程默认处理方式是终止。所以cat huge.log | head -n 10经常会出现broken pipe提示但这是正常的不是事故。还有一道辨析题tail -n 1 file和head -n -1 file有什么区别前者取最后一行后者取“去掉最后一行后的全部内容”。一个是只留尾部一个是剔除尾部两者结果完全不同。把这题答对说明你确实理解了正负数参数的行为差异。5. 踩坑记录管道信号、二进制文件和数据丢失5.1 看到broken pipe别慌第一次在管道里看到broken pipe提示的人十有八九会以为命令报错了。我见过有同事因为这个提示把排查方向带到沟里去耗费了几个小时。解释一下当管道下游的head提前退出上游cat或grep还在写数据时内核会发送SIGPIPE信号终止上游进程同时stderr会打出broken pipe。这个行为不是head的bug而是所有基于管道的“提前截断”操作的必然副作用。所以当你执行cat huge.log | head -n 10看到broken pipe一点问题都没有前10行早就打印出来了。更好的做法是干脆别用cathead -n 10 huge.loghead自己就能打开文件没必要多一个进程、多一次全量读取。这个习惯一旦养成能减少大量无意义的IO和报错噪音。5.2 head -c 二进制文件会把终端搞乱直接执行head -c 512 binfile终端可能会收到一堆不可见控制字符轻则光标乱跳重则改变终端标题、颜色甚至显示模式。我犯过一次当时把一个ELF可执行文件的前几千字节直接打进了终端屏幕直接花掉连提示符都变形了只能reset救场。正确处理是配合xxd、od或file让字节以十六进制或文本形式展示head -c 64 binfile | xxd head -c 16 binfile | od -A x -t x1z还有一个和-c相关的坑按字节截取多字节字符会“断字”。一个中文UTF-8字符占3个字节head -c 2 file可能只截到半个字符输出串会变成乱码。如果是纯文本预览尽量用-n按行取不要用-c按字节取。5.3 head重定向把自己文件写空这是head最容易造成数据丢失的用法head -n 20 file.txt file.txtShell在执行重定向时会先以“创建/截断”模式打开目标文件也就是说file.txt在第一毫秒就已经被清空了。等到head再去读它读到的是空文件最后输出0行原文件内容全没了。我在生产环境见过有人这样处理配置文件后果是直接清空了一个有大量注释的备份文件。正确姿势是写临时文件再覆盖head -n 20 file.txt tmp.txt mv tmp.txt file.txt如果你用的是vim、sed这类工具它们内部会处理这个“读原文件写临时文件再替换”的流程但直接用shell重定向时必须自己绕开这个坑。还有一个更隐蔽的版本head -n 20 archive.tar.gz archive.tar.gz即使源文件是二进制打包数据也一样会被清掉。这个习惯必须戒掉任何xxx file file的写法都应该怀疑。5.4 head读“无限输出”管道时的依赖head在管道里虽然能提前终止上游但上游是否真的能及时收到SIGPIPE取决于上游进程是否在“写管道时”被信号打断。如果上游是一个持续输出但不检查写入错误的进程理论上它可能不会立刻退出只是后续写入全部失败。不过实际绝大多数标准工具都会正常退出。一个更常见的连带问题是在脚本或CI流水里使用cmd | head时set -o pipefail会让最终退出码变成上游的失败退出码。因为head正常退出了但上游cat/grep因为SIGPIPE而被信号杀死退出码是非零。这就是“明明head成功为什么脚本认为失败”的根源。我的规避方案如果是坚定要截断的场景用grep -m N替代grep | head如果是取文件前几行直接head读文件不要cat管道。只在确实需要“不挑明上游类型、通用截断”时才用cmd | head并且清楚pipefail行为。6. 冷知识与小技巧让head融入日常习惯6.1 用head做数据采样和测试head不只是给文件用的流式生成工具的输出也能被它截断。比如快速生成一组测试数字seq 1 1000000 | head -n 10这比完整执行seq再截取高效得多因为你实际上只需要前10个。批量预览多个文件时省略-n也可以默认是10行。在脚本里逐文件看前缀for file in *.conf; do echo $file ; head -n 2 $file; done这个循环在检查一批配置文件格式是否统一、头部注释是否存在时非常实用。6.2 head与sed/awk的选型权衡有人问head能做的sed -n 1,20p也能做为什么还要用head我的答案是看场景。语义清晰head就是“取头部”读代码的人一眼明白sed的-n 1,20p需要多一层心智负担。字节级截取sed是按行的做不到head -c 1M这种精确字节操作。管道语义head作为管道末端表达“我要前N条”非常自然。但如果需求是“从第21行开始取20行”sed -n 21,40p file一次完成而headtail是两个进程两道扫描后者反而绕路。工具没有一个银弹明白各自的能力边界才是高效的人。6.3 把head固化到操作习惯里我把head和file组合封装成了一个叫preview-type的函数放在~/.bashrc里function ftype() { head -c 512 $1 | file - }使用的时候ftype unknown_001 ftype archive.dat ftype dump.bin一条命令识别文件类型省掉反复猜测的时间。在实际工作里我把head和这个组合固化成习惯后最明显的感受是越来越少因为“打开超大文件卡死”而焦虑。无论是日志、数据文件、配置文件还是二进制文件head几个组合拳就能覆盖大部分日常。遇到需要“先看看再决定”的场景第一反应就应该是head而不是编辑器。这个习惯建议你也从今天开始练。