首页 / 资讯中心 / 文章详情

Linux文件类型识别神器:file命令原理与实战

Linux文件类型识别神器:file命令原理与实战 ★ FEATURED ARTICLE
在 Linux 下跟文件打交道多了你就会发现一个颠扑不破的真相扩展名只是给人类看的备注不是文件真实身份的凭证。从网上下载的“教程.txt”可能是个压缩包从设备导出的“firmware.bin”也许是 TAR 归档更别说各种脚本、日志和图片混在一个目录里根本看不出谁是谁。这时候file文件类型检测就是我最常用也最顺手的第一道识别工具。它不看你文件名而是直接读文件内容里的字节特征再跟系统内置的规则库比对快速告诉你这个文件到底是什么。file命令最大的价值是“快”和“准”。它不需要你把文件打开不需要装什么重型软件也不需要知道文件之前叫什么名字一条命令下去几毫秒内就能给出结论。不管你是运维排查日志目录、逆向分析固件、清理磁盘垃圾还是做安全样本分类都会反复用到它。这篇文章我会从原理讲到实战把file命令的常用参数、底层机制、脚本集成和常见坑一次性说透。1. 为什么需要 file 命令1.1 扩展名不可靠改个后缀骗不了 file很多人刚接触 Linux 时会有一个惯性思维文件类型看后缀就行。.txt是文本.jpg是图片.sh是脚本。但真实场景里扩展名是最不值钱的信息因为随便一个人都能用mv把文件后缀改掉很多下载工具、传输协议也会把文件名写成无意义的哈希串。我自己就撞到过这么一件事某台服务器上的日志目录里躺着一堆没后缀的文件是某采集程序定期打包上传的文件名全是时间戳加随机数字。开发同事说这些是文本日志我第一反应就是拿file验一下结果里面一半是 gzip 压缩数据一半是 SQLite 数据库真正纯文本的反而没几个。要是当时直接按.log后缀去处理后面全都得返工。换一个更简单的例子$ mv report.pdf report.txt $ file report.txt report.txt: PDF document, version 1.4看到没有后缀从.pdf改成.txtfile依然能通过文件头识别出这是 PDF。这也是我经常跟新人强调的不要相信文件名要相信文件内容。1.2 文件指纹魔数Magic Numberfile凭什么能做到这一点靠的是文件内容里的“指纹”也就是常说的魔数Magic Number。程序文件、图片、压缩包、可执行文件在设计格式时都会在文件开头或者某个固定偏移位置写入一段固定的字节序列用来标记自己的格式。file就是靠匹配这些字节序列来判型。举几个最典型的文件类型十六进制特征人类可读标记PDF25 50 44 46%PDFJPEG 图片FF D8 FF无二进制特征PNG 图片89 50 4E 47 0D 0A 1A 0A以不可见控制字符起始ELF 可执行文件7F 45 4C 46\x7fELFZIP 压缩包50 4B 03 04PK\x03\x04gzip 压缩数据1F 8B无二进制特征常见 shell 脚本23 21#!你可以在文件开头上做文章也可以在某一段结构的固定偏移处找特征。比如 tar 归档的特征字符串ustar并不在文件头部而是出现在偏移 257 字节的位置。所以file的匹配规则不是简单地“对前几个字节”而是支持指定偏移量、指定类型、指定正则表达式的一套完整规则引擎。理解了这一点再看后面的章节就会很顺。1.3 它到底“看不看”文件内容一句话file从不依赖文件名它内部会读取文件内容按偏移量取片段、做二进制比对、做字符串匹配、做编码试探最后把命中的规则对应的人类可读描述输出给你。对普通文件它读的是文件字节流对特殊文件比如块设备它用-s参数直接去读设备原始数据而不是只看 stat 输出里的文件类型。2. 基础用法与高频参数2.1 最常用的几种调用方式最基础用法直接给文件名$ file unknown.bin unknown.bin: gzip compressed data, from Unix, original size modulo 2^32 10240输出格式一般是“文件名: 类型描述”。如果嫌文件名碍事用-b只显示描述部分$ file -b unknown.bin gzip compressed data, from Unix, original size modulo 2^32 10240想拿到更适合程序解析的 MIME 类型用-i有的版本里写作--mime$ file -i unknown.bin unknown.bin: application/gzip更稳定的做法是用--mime-type只输出 MIME 类型不带 charset$ file --mime-type unknown.bin unknown.bin: application/gzip一次查多个文件也很自然直接全部堆在后面即可。如果文件列表放在一个文本文件里可以用-f$ file -f list.txt每条记录一行file会逐行读取并当作文件名去检测。配合-0还能让输出以空字符分隔方便接到xargs -0里处理奇葩文件名。2.2 参数速查表参数这块不需要死记用的时候查表就行。我整理了一个自己经常翻的速查表参数作用使用示例-b只显示类型描述不显示文件名file -b test.sh-i输出 MIME 类型含 charset 时也一起带file -i test.sh--mime-type只输出 MIME 类型适合脚本判断file --mime-type test.sh-f FILE从 FILE 读取待检测文件列表file -f list.txt-L跟随符号链接检测链接指向的真实文件file -L /usr/bin/python3-h不跟随符号链接只看链接本身file -h /usr/bin/python3-s检测特殊文件如设备文件的原始内容file -s /dev/sda-z对压缩文件尝试查看内部内容file -z archive.zip-k不命中第一条就停下输出所有匹配结果file -k sample.bin-m FILE使用自定义 magic 规则文件file -m myrule.mgc test.bin-C编译 magic 规则文件为 .mgcfile -C -m myrule-d调试模式打印匹配过程file -d -m myrule test.bin-0文件名后追加空字符方便 xargs -0file -0 -f list.txt2.3 符号链接与特殊文件-L / -h / -s / -z这里有一个容易踩的小坑。不同发行版对符号链接的默认行为并不完全一致很多系统上你直接执行file /usr/bin/python3看到的是一行/usr/bin/python3: symbolic link to python3.11也就是说它默认停在“这是一个符号链接”这一步不再往下追。如果你想知道链接背后的目标文件真实类型加-L$ file -L /usr/bin/python3 /usr/bin/python3: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.17, stripped如果你就是想知道链接本身的信息可以显式加-h但大多数发行版默认就是这个行为所以记一条经验看到 symbolic link 输出且想继续深挖时补一个 -L。-s参数平时用得少但查块设备或者裸设备时很管用。对/dev/sda这类设备file默认只显示block special不会真的去读盘内容。加上-s后它会尝试读取设备的原始数据识别文件系统类型或分区签名。-z则用来处理压缩包的双层结构。压缩文件本身类型是gzip compressed data但里面装的是什么-z会尝试解压一层再继续识别类似“剥洋葱”。$ file -z app.tar.gz app.tar.gz: POSIX tar archive (gzip compressed data)3. 从字节到结论file 的内部机制3.1 magic 规则库藏在系统里的“字典”file之所以能识别成千上万种文件格式不是因为它硬编码了这些格式而是因为它读取了一个名为 magic 的规则库。这个规则库在大部分 Linux 发行版上位于/usr/share/file/magic.mgc或/usr/share/misc/magic.mgc是编译后的二进制索引文件。想直接看人类可读的规则源码可以找同目录下的magic文件。每条规则大致长这样0 string PK\003\004 Zip archive data逐列拆开解释第一列是偏移量说明要匹配的位置第二列是数据类型string表示按字符串匹配其他常见类型还有byte1字节、short2字节、long4字节、date时间戳等第三列是期望匹配到的内容第四列是匹配成功后输出的描述文本。file执行时的行为就是打开 magic 库逐条规则与文件内容比对一旦匹配上输出对应的描述然后停下。默认匹配到第一条就停所以输出通常是一个“最可能的类型”加上-k才继续把所有匹配结果全部列出来。3.2 自定义规则和编译有些场景下系统自带规则不够用比如团队内部有一种自定义的二进制固件格式文件头固定是0x434F4445对应字符串CODE想在file里也能识别出来怎么办自己写规则即可。先创建一个文本文件假设叫myrule.magic0 string CODE MyCompany Firmware Image然后直接用-m指定这个规则文件测试$ file -m myrule.magic firmware_v1.bin firmware_v1.bin: MyCompany Firmware Image确认规则没问题想把规则合并进正式的 magic 库里用-C编译$ file -C -m myrule.magic这会在当前目录生成myrule.magic.mgc之后使用file -m myrule.magic.mgc就能读编译后的规则。如果机器上还有其他工具也想复用这套规则可以把.mgc文件放到/usr/share/file/之类的目录里或者放进MAGIC环境变量指定的路径。规则还可以更复杂比如附加二级匹配来输出更多信息0 string CODE MyCompany Firmware Image 4 byte x version %d第一行匹配到CODE之后第二行的表示在已经命中主规则的基础上继续检查偏移 4 处的字节并把版本号带出来。这种嵌套写法在生产环境很实用自定义文件类型能一眼看出版本信息。3.3 用 -d 观察 file 内部在干嘛想知道file到底是怎么匹配上的可以开调试模式$ file -d -m myrule.magic firmware_v1.bin调试模式下它会把每一条规则的匹配过程和结果打到标准错误输出包括尝试的偏移量、读取的字节、比对结果。我第一次看输出的时候有点震惊原来它能识别这么多格式靠的就是一条条傻笨的规则硬比对。但也正因为这种“规则遍历”的工作方式规则写得越多执行耗时越长。对海量文件做批处理时如果机器性能紧张这点值得留意。4. 实战典型文件检测过程实录4.1 先看字节xxd 把魔数贴出来file是黑盒结论但排查问题的时候我习惯把证据也拉出来双管齐下。最常用的辅助工具是xxd或hexdump。$ xxd unknown.dat | head -n 2 00000000: 504b 0304 1400 0000 0000 0000 0000 0000 PK.............. 00000010: 0000 0000 0000 0000 0000 0000 0000 0000 ................看到PK\x03\x04这个开头基本可以断定是个 ZIP 系压缩包。这时候再看file的输出$ file unknown.dat unknown.dat: Zip archive data, at least v2.0 to extract结论和字节证据对上了后面处理起来就踏实。4.2 ELF 可执行文件与脚本的差别绝大多数的 Linux 可执行文件都是 ELF 格式。file对 ELF 的输出很丰富能看出架构、位数、是否动态链接、是否去除符号表$ file /bin/ls /bin/ls: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.17, BuildID[sha1]..., stripped如果是静态编译的二进制它会显示statically linked如果没 strip会显示not stripped。这些信息在排查“为什么这个程序在这台机器上跑不起来”时非常有用比如拿到一个 ARM 架构的二进制放到 x86 机器上file一眼就能拆穿。脚本类文件的检测就更有意思了。带 shebang 的脚本通常会被识别为对应语言的脚本$ file deploy.py deploy.py: Python script, ASCII text executable $ file run.sh run.sh: Bourne-Again shell script, ASCII text executable注意这里的识别依据是文件第一行的#!以及紧随其后的解释器名称。如果脚本第一行写的是#!/usr/bin/env python3有些file版本会输出一个比较奇怪的描述因为规则库里未必专门适配过env这种间接写法。4.3 压缩包与复合文档不止一层皮压缩文件是重灾区。ZIP、gzip、bzip2、xz、7z 各有各的特征用file一眼分清压缩格式文件头特征file 输出ZIP50 4B 03 04Zip archive datagzip1F 8Bgzip compressed databzip242 5A 68bzip2 compressed dataxzFD 37 7A 58 5A 00XZ compressed data7z37 7A BC AF 27 1C7-zip archive data复合文档更有意思。.docx、.xlsx、.pptx这些 Office 文件本质上是 ZIP 容器里面是 XML 文件。新版本file会进一步解析容器内部结构输出Microsoft Word 2007之类的结果老版本则可能只报Zip archive data。遇到版本比较旧的系统时别急着下结论可以结合unzip -l查看内部文件确认。5. 在脚本中集成 file自动化判断文件类型5.1 条件分支用 MIME 类型做分流file真正的威力在自动化场景。比如我有一个上传目录用户传上来的文件乱七八糟但业务只收图片。写脚本时用--mime-type做条件判断非常稳定#!/usr/bin/env bash for f in /upload/unknown/*; do [ -f $f ] || continue mime$(file -b --mime-type $f) case $mime in image/jpeg|image/png|image/gif) echo [OK] $f - $mime mv $f /upload/images/ ;; application/pdf) echo [OK] $f - $mime mv $f /upload/pdf/ ;; text/*) echo [SKIP] $f - $mime ;; *) echo [??? ] $f - $mime ;; esac done这里用-b去掉文件名前缀直接拿 MIME 字符串做比对。实测下来--mime-type比-i更适合脚本因为输出更简洁不会有text/plain; charsetus-ascii这种带分号尾巴的情况case 匹配写起来省心很多。5.2 批量清理未知扩展名文件另一个高频需求是把一堆无扩展名文件按真实内容分类改名。比如设备导出的日志文件有的是纯文本、有的是 gzip 压缩、有的是 JSON、有的是 SQLite 数据库。用find加循环处理find /data/raw -type f -print0 | while IFS read -r -d f; do mime$(file -b --mime-type $f) case $mime in text/*) ext.log ;; application/gzip) ext.gz ;; application/json) ext.json ;; application/vnd.sqlite3) ext.db ;; *) ext.unknown ;; esac mv $f ${f}${ext} done我在测试时特意用了带空格和特殊字符的文件名做验证所以用-print0搭配read -d 这比for f in $(find ...)安全得多不会把文件名按空格劈成好几段。5.3 性能与集成注意点每次调用file都会启动一个子进程批量处理几千个文件时没问题但如果面对几十万个文件进程启动开销就凸显了。更高效的方案是用libmagic直接内嵌调用比如用 Python 的python-magic库import magic m magic.Magic(mimeTrue) print(m.from_file(sample.bin))很多系统依赖这个库做文件类型识别。命令行版本和库版本底层的 magic 规则库基本一致但库方式省去了反复 fork 进程的消耗对大规模扫描更友好。不过库版本输出的细节控制没命令行那么直观需要自己处理异常、路径等边界情况。6. 常见问题与避坑实录6.1 问题速查表平时群里被问得最多的问题基本都能归进下面这张表现象常见原因解决思路file输出data规则库没匹配到已知格式用xxd看文件头搜索特征或补充自定义规则脚本识别成ASCII text而不是 Pythonfile版本规则不全或 shebang 写法特殊升级file或用--mime-type辅助判断符号链接输出symbolic link to默认未跟随链接加-L查看目标真实类型大文件检测很慢规则库太大或文件需读多次用-k减少批处理次数时反而更慢建议换libmagic或优化流程gzip 压缩包内层类型未识别file默认不收内层加-z尝试解压并识别内层老版本报表不识别 docx/xlsxOffice 格式是 ZIP 容器旧规则库没解析内部 XML更新 magic 库或临时用unzip -l辅助6.2 为什么脚本识别不出脚本语言我碰到最多的一个具体场景是写好的 TCL 脚本、Ruby 脚本file只报ASCII text却不报具体语言。原因通常是系统自带的 magic 规则库里没有对应语言的规则或者规则匹配条件比较严格。不同版本的file差异也很大有的能识别 Python有的只能笼统说script text executable。这种情况下别慌。先用head -n 1 file看 shebang再决定用什么解释器执行。file只能作为参考线索不能把它当成绝对真理。如果确实需要让file识别出错可以按 3.2 的方法写自定义规则把#!/usr/bin/env foo这种开头映射到自定义类型。6.3 安心使用但别盲目相信这个坑要重点说魔数是可以伪造的。file匹配的是特征字节不是密码学验证。往一个文本文件开头塞一串%PDF字符file就会把它识别成 PDF往一个二进制文件头塞PK\x03\x04它就会被认为是 ZIP。这不是 bug而是文件格式识别技术本身的先天限制。我在做安全分析的时候从不会把file的结果当作唯一依据。收到一个可疑文件正确姿势是file先看类型再用xxd观察文件头用strings看字符特征必要时交给binwalk、clamscan这类专业工具深入分析。把它当成“第一个线索”而不是“最终结论”才能避免被精心构造的文件绕进去。最后分享一个小技巧如果你长期靠file处理各种新式文件格式记得定期升级file软件包并更新 magic 规则库。新格式层出不穷系统自带的规则库更新往往滞后。遇到识别成data的文件先别着急下结论查一下该格式最新的魔数特征自己补一条规则往往比到处找转换工具更快。
阅读完成 · 觉得有帮助?
咨询建站