很多 Linux 初学者学到“文件操作命令”这个主题时,很容易产生一个错觉:文件操作不就是 ls、cd、cp、mv、rm 这一套吗?坦白说,这些确实是最高频的命令,但如果只停留在“增删改查 + 移动复制”的层面,等真正进到生产环境或自己折腾服务器时,很快就会卡住。比如日志文件几百兆,cat 一下终端直接刷到怀疑人生;想在配置目录里找某个参数,却不知道用什么命令精确搜;想把某个目录打包传走,结果 tar 的用法全忘光。这一篇就接着文件操作命令(一)的内容继续往下走,把和文件关系最紧密、日常最常用的另一批命令一次讲透。
这篇东西适合谁?刚学完基础命令、想知道下一步该学什么的人;也适合工作里偶尔要碰 Linux、但一直靠“背命令”混日子的朋友。我会把每条命令放在实际场景里讲,比如为什么用 less 而不是 cat,为什么 uniq 之前非要配 sort,为什么 tail -F 比 tail -f 更适合在生产环境盯日志。这些都是只有实操过才会注意到的细节。
1. 从查看内容开始:cat、tac、less、head、tail怎么选
1.1 cat:最直接的输出工具
cat 是 concatenate 的缩写,本意是“连接文件”。所以它的第一个用途就是把多个文件接在一起输出,第二个用途才是查看单个文件内容。很多人只记得 cat 能看文件,却忘了它本身就是为了拼接而生的。
cat /etc/nginx/nginx.conf # 查看文件 cat a.txt b.txt > c.txt # 把 a.txt 和 b.txt 拼起来写入 c.txt cat -n /etc/nginx/nginx.conf # 输出时显示行号-n这个参数在排错时特别好用。比如配置文件报错第 42 行,你直接cat -n打开,行号对上,定位就快了。
但 cat 有个致命弱点:它会把整个文件一股脑输出到终端。文件小还好,文件一大,终端会被几千行内容瞬间刷屏,想往回翻都不方便。如果你拿到的是一个几十 MB 的日志文件,千万别直接 cat。还有一些新手会拿 cat 去读二进制文件,终端会输出一堆乱码甚至触发控制字符,屏幕直接卡住。遇到这种情况,可以直接按Ctrl+C中断。
1.2 less:查看大文件时别用cat
less 才是我平时查看大文件的主力。它的好处是分页显示,不会一次性把所有内容倒出来。
less /var/log/nginx/access.log进入 less 之后,常用操作就这几个:空格键向下翻页,b向上翻页,/关键词向下搜索,?关键词向上搜索,q退出。如果你需要边看边追线索,比如排查日志中某次请求的完整链路,less 比任何编辑器都顺手。
这里要顺便提一下 more 这个老命令。早期 Unix 时代的 more 只能向下翻页,不能往回翻,功能非常受限。后来出现了 less,名字本身就是一句玩笑话——“less is more”的反向演绎。实际用下来,less 功能完全覆盖 more,所以我的建议很简单:忘掉 more,直接用 less。
less 还有一个常用搭档场景,就是配合管道命令查看长输出。比如:
ps aux | less docker logs 容器名 | less只要输出内容可能超过一屏,我都会习惯性加个| less。这个习惯能救你很多次,尤其是在终端窗口不够大的时候。
1.3 head/tail:只看开头和结尾
很多时候你根本不需要完整的文件内容,只要看开头几行确认格式,或者看结尾几行确认最新状态。head 和 tail 就是干这个的。
head -n 20 /etc/passwd # 查看文件前20行 tail -n 50 /var/log/syslog # 查看文件最后50行tail 在服务器日志场景里还有个杀手级用法,就是-f参数。-f是 follow 的意思,它会持续监听文件末尾的新增内容,非常适合实时观察日志输出。
如果用tail -f盯着 nginx 日志,你会发现每次页面刷新,终端就会多出一行访问记录。这在调试接口的时候非常直观,能看到请求进来、状态码、耗时等实时信息。
但这里有个很坑的细节:如果日志文件系统触发了 logrotate(日志轮转),旧文件会被改名,新文件会重新生成。这时候你用tail -f跟踪的其实是旧文件的文件描述符,新日志不会再进入你的屏幕。正确做法是使用tail -F,也就是大写 F,它会按文件名重新打开文件,日志轮转后依然能接着跟。所以我的习惯很简单:生产环境一律用大 F。
2. 统计、排序、去重:sort、uniq、wc三板斧
2.1 wc:给文件量尺寸
wc 是 word count 的缩写,用来统计文件里的行数、单词数和字节数。
wc -l /etc/passwd # 只统计行数 wc -w /etc/nginx/nginx.conf # 统计单词数 wc -c /tmp/a.txt # 统计字节数wc -l是我用得最多的参数,经常用来快速确认一个文件有多少行。比如我写脚本时想确认 IP 白名单文件里到底有多少条规则,一条命令就出结果,不用打开文件去数。
这里有个容易忽略的点:wc -c统计的是字节而不是字符。一个 ASCII 字符占 1 字节,但一个中文字符在 UTF-8 编码下占 3 字节。所以如果你统计一个中文文本文件,wc -c的数字会明显大于肉眼数出来的字符数。这不是命令出错,是编码机制决定的。反过来,如果只需要看字符数,就得用wc -m。
2.2 sort:排序的隐藏技能
sort 默认按字典序排序,也就是按照字符编码顺序排。这个默认行为有时候会产生一些“反直觉”的结果,比如数字 10 会排在数字 2 前面,因为按字符比较,'1' 小于 '2'。
sort /etc/passwd # 按字典序排序 sort -n nums.txt # 按数字大小排序 sort -r nums.txt # 倒序排列 sort -k 2 -t ',' student.txt # 按第2列排序,分隔符是逗号-k指定按第几列排序,-t指定列之间的分隔符。这在处理 CSV、日志等格式文本时非常实用。比如按时间列给访问日志排序,或者按 IP 地址段排序,都可以用这两个参数组合完成。
sort 还有一个容易被忽略的选项-u,作用是在排序的同时去重。它和 uniq 的关系很密切,后面会讲到。不过我的建议是:如果数据量不大,直接sort -u是最省事的;如果还要统计每个重复项出现了多少次,那就必须走 sort + uniq 的组合。
2.3 uniq:去重必须配合sort
uniq 这个名字看着很简单,但实际用的时候有个大坑:它只会去除“连续相邻”的重复行。也就是说,如果重复内容分散在文件不同位置,uniq 根本不会理你。
sort file.txt | uniq # 先去重再去掉重复行 sort file.txt | uniq -c # 统计每行出现次数 sort file.txt | uniq -d # 只显示重复行uniq -c这个组合非常强大。它能把每行内容出现的次数统计出来,最经典的应用场景就是分析访问日志里的 IP。比如你想知道哪个 IP 访问次数最多,直接:
grep "2025-01-01" access.log | awk '{print $1}' | sort | uniq -c | sort -rn | head这条命令把日志里第一列(通常是 IP)提取出来,排序后去重统计次数,再按次数倒序排,最后看前几个。整个过程就是文件操作命令的完美协作,每一步单独看都很基础,但组合起来能解决一个很现实的分析需求。这也是我一直强调的:Linux 命令不是靠背,而是靠理解每个工具的边界,然后像管道一样把它们串起来。
3. 按内容找文件:grep,以及文件对比diff
3.1 grep:按内容捞文件
很多新手找文件只会用 ls 一层层翻,或者到图形界面里搜索。但在 Linux 服务器上,图形界面经常没有,效率最高的方式就是两条路:按文件名找用 find,按文件内容找用 grep。
grep 的本职是在文件内容里匹配关键词。它支持正则表达式,所以能玩的花样非常多。
grep "error" /var/log/syslog # 在单个文件里找 grep -i "error" /var/log/syslog # 忽略大小写 grep -n "error" /var/log/syslog # 显示行号 grep -r "error" /var/log/nginx/ # 递归搜索整个目录 grep -rn "error" /etc/nginx/ # 递归且显示行号 grep -c "error" /var/log/syslog # 统计匹配行数 grep -v "debug" app.log # 反向匹配,排除包含debug的行-A、-B、-C这三个参数也很关键。它们用来打印匹配行的上文、下文或上下文行数。比如排查报错日志时,只知道错误行本身往往不够,还要看错误发生前后发生了什么。
grep -C 5 "FATAL" app.log这会把匹配到 FATAL 的行以及前后各 5 行一起输出,整个上下文一目了然。日志排错场景里,这个命令的使用频率远高于单纯的 grep。
再补充一个小技巧:grep -rl "关键词" 目录可以罗列出包含该关键词的所有文件名。比如你想确认哪个配置文件里写了某个数据库连接串,一条命令就能把所有涉及的配置文件全部列出来,不用一个个打开翻。
3.2 diff:对比文件和目录
diff 这个命令平时用的人不算多,但一用到就是关键时刻。它用来比较两个文件或两个目录之间的差异。
diff file1.txt file2.txt diff -u file1.txt file2.txt # 输出统一格式的差异,方便阅读 diff -r dir1 dir2 # 递归比较两个目录diff 的原始输出格式有点难懂,用<表示第一个文件独有的行,>表示第二个文件独有的行,a、c、d分别代表新增、修改、删除。虽然逻辑清晰,但实际看起来不直观,所以我更推荐diff -u。
diff 最常见的实际用途是确认配置变更。比如你改了 nginx 配置后,想知道具体改了哪里,可以把改之前和改之后的文件各存一份,然后 diff 一下。用diff -u输出的内容可以直接拿给同事看,要点一目了然,比聊天记录里贴大段配置强太多了。
还有一个我经常用的细节:diff 命令的退出码非常明确,0 表示两个文件相同,1 表示有差异,2 表示执行出错。所以脚本里经常会这样写:
diff file1 file2 >/dev/null if [ $? -eq 0 ]; then echo "文件一致" else echo "文件有变化" fi这类脚本常用于监控配置是否被意外修改,虽然简单,但很可靠。
4. 找文件位置:find、locate、which、whereis
4.1 find:搜索文件的瑞士军刀
find 应该是文件查找命令里的重头戏。它的语法看起来有点复杂,第一眼可能吓到人,但掌握了套路之后非常顺手。
find 的基本结构是:
find 起点路径 匹配条件 处理动作find /home -name "*.log" # 按文件名匹配 find /etc -type f # 只找普通文件 find /var/log -type d # 只找目录 find . -mtime -7 # 最近7天内修改过的文件 find . -size +100M # 超过100MB的文件实际工作中我经常把条件组合在一起用。比如找出 /var/log 下最近半天内被修改过的 .log 文件,然后看看大小:
find /var/log -name "*.log" -mtime -0.5 -type f -exec ls -lh {} \;注意-exec这个参数,它会对每个搜索结果执行后面的命令。{}是一个占位符,代表当前找到的文件,\;表示命令结束。很多人第一次写这个都会漏掉结尾的反斜杠和分号,结果报错。
不过现在 GNU 版本的 find 也支持-delete,可以直接删除匹配到的文件:
find /tmp -name "*.tmp" -mtime +7 -delete这条命令会删除 /tmp 下七天前修改过的所有 .tmp 文件。但我要认真提醒一句:-delete很危险,一旦执行没有后悔药。我的习惯是先用不带-delete的命令跑一遍,确认输出结果是预期中的文件,再决定是否真要删。也可以先-print看结果,再替换成-delete,这样至少多一道确认。
4.2 locate:靠索引的快速路径
find 功能强,但它每执行一次都会扫描整个目录树,在大目录里搜索会比较慢。如果你要的是“秒出结果”,可以试试 locate。
locate 的工作方式不是实时扫描磁盘,而是查询一个预先构建好的文件名数据库。这个数据库由 updatedb 命令定期生成。因为查询的是索引,速度比 find 快得多:
locate nginx.conf locate access.log但代价就是它不是实时的。刚创建的文件可能还不在数据库里,直接 locate 会找不到。解决办法是先手动更新一次数据库:
sudo updatedb执行完再 locate,就能看到新文件了。
我自己的使用习惯是:临时小范围查找用 find,因为不用提前维护数据库;如果是系统范围大搜索,找某个配置文件或日志文件,用 locate 更高效。还有一点要注意:有些最小化安装的 Linux 发行版默认没装 locate,如果提示找不到命令,可能需要先安装 mlocate 或 plocate 包。
4.3 which与whereis:定位命令本身
前面几个命令找的是文件,which 和 whereis 定位的是命令本身。比如你输入nginx回车执行,系统到底跑到哪里去找到这个程序呢?用 which 就能看:
which python which nginx它会在 PATH 变量指定的目录列表里逐个查找,第一个匹配到的目录就是命令的真实位置。这在排查“我明明装了软件,为什么输命令没反应”或者“系统里有多个版本,到底用的是哪一个”时非常关键。
whereis 和 which 的区别在于,whereis 会同时搜索二进制文件、源代码目录和 man 帮助文档,而且它不只是看 PATH,还会去很多固定的标准安装路径里找。
whereis nginx输出通常类似:
nginx: /usr/sbin/nginx /usr/share/man/man8/nginx.8.gz如果看到输出里只有 man 手册而没有二进制文件路径,那就要警惕了:很可能软件装了手册相关的文件,但主程序没装,或者装到了非标准路径里。这时候再用 which 或 find 补查一下,思路就很清晰了。
5. 权限与链接:chmod、chown、ln的日常玩法
5.1 chmod:修改权限
Linux 下每个文件都有三组权限:所有者(user)、所属组(group)、其他人(others)。每组的权限又分为读(r)、写(w)、执行(x)。这种设计看着简单,实际碰过的朋友都知道,权限问题经常是最隐蔽的坑。
chmod 有两种写法。最常用的是数字法,因为简单粗暴:
chmod 755 script.sh chmod 600 id_rsa chmod 644 readme.txt数字法的原理是把每组权限转换成二进制位。读是 4,写是 2,执行是 1,加起来的和就是最终权限。所以 7 表示读写执行,5 表示读和执行,4 表示只读,6 表示读写。
另外一种是符号法,更灵活:
chmod u+x script.sh # 给所有者加上执行权限 chmod g-w project.txt # 去掉组的写权限 chmod o=r report.txt # 给其他人设置为只读符号法适合微调,数字法适合直接设定完整权限。我的建议是:需要精确控制时用数字法,比如设置私钥为 600;临时调整某个权限位时用符号法。
这里要特别提醒:不要无脑chmod 777。给所有用户开放全部权限,等于把文件的大门彻底拆掉,尤其对于公开目录下的脚本、配置甚至数据库文件,风险很高。我见过很多新手为了省事,把网站目录全部 777,结果被扫描工具盯上,文件被篡改。正确的做法是:能不给写权限就不给,能不给执行权限就不给。按需授权才是安全底线。
5.2 chown:修改属主和属组
chown 用来改变文件的所有者和所属组。这个命令平时用得不算多,但一旦涉及多用户协作或软件部署,就会立刻碰到。
chown user1 file.txt # 只改所有者 chown user1:group1 file.txt # 同时修改所有者和所属组 chown -R www-data:www-data /var/www # 递归修改整个目录注意,普通用户一般不能把文件的所有者改成别人,只有 root 或通过 sudo 才能执行这种操作。原因是系统要防止一个用户把文件“甩锅”给其他人,这涉及安全和配额管理。
-R递归参数在高频使用场景里非常重要。比如你部署网站后,nginx 以 www-data 用户运行,而文件是你用 root 拷贝过去的。如果不做chown -R www-data:www-data,nginx 就可能没有权限读取文件,导致网站出现 403 或者静态资源加载不出来。这类问题排查起来挺耗时,但原因往往就这么简单。
5.3 ln:硬链接与软链接
链接这个概念让很多人发懵。简单理解,文件在磁盘上有一个 inode 节点,里面存着文件的元信息和数据块位置。文件名只是指向 inode 的一个入口。
ln 创建硬链接时,本质上是在目录里新增一个指向同一个 inode 的文件名。两个文件名看起来不同,但内容、修改时间、权限全部共享:
ln /tmp/source.txt /tmp/hardlink.txt用ls -li看一下,两个文件前面的 inode 编号完全一样。硬链接最实在的价值是:如果删除其中一个文件名,只要还有另一个文件名指向同一个 inode,数据就不会丢。
但硬链接有两个明显限制:不能跨文件系统创建,也不能给目录创建。跨文件系统意味着两个目录可能在不同的磁盘分区上,inode 编号体系不通用。这些限制在实际使用中挺麻烦,所以更常见的是符号链接,也就是软链接:
ln -s /opt/app/current /home/user/app_link软链接和 Windows 的快捷方式很类似,它本身是一个独立的文件,里面存着目标路径。删除软链接不影响目标文件,但如果目标文件被移走或删除,软链接就成了断链,访问会报 No such file or directory。
部署场景里软链接用得非常多。比如服务器上装了多个版本的应用,目录结构通常是/opt/app-1.0、/opt/app-2.0,然后建一个/opt/app软链接指向当前使用的版本。升级时改一下链接指向,业务代码不用动,回滚也一样简单。
6. 归档和压缩:tar、gzip、zip全家桶
6.1 tar:最常用的打包工具
tar 被称为“磁带归档工具”,最开始确实是为了把数据写到磁带上的,但现在它的意义更多是“打包目录”。tar 本身不压缩,它只是把一堆文件拼成一个文件。真正压缩的时候,通常会加上 gzip 或 xz。
最常见的三种用法:
tar czf backup.tar.gz /home/user/data # 打包并压缩 tar tzf backup.tar.gz # 查看包里有什么文件 tar xzf backup.tar.gz -C /tmp/backup # 解压到指定目录参数里c是创建(create),x是解包(extract),t是列表(list),z表示用 gzip 压缩,f表示后面跟着文件名。-C指定解压目标目录,特别建议用,否则 tar 会直接在当前目录里解出所有文件,把当前目录搞得一团乱。
如果是压缩纯文本、日志这些高度可压缩的数据,gzip 的效果非常明显。但如果是已经压缩过的文件(比如 .jpg、.mp4、.png),再 tar 后压缩也压不了多少,反而浪费时间。所以做备份时要分清楚:你的数据到底能不能压出效果。
tar 还有一个细节是解包路径。如果 tar 包里的文件路径是带目录前缀的,比如backup/2025/...,那你解压的时候看到的目录结构就比较清楚。但如果别人打包时没注意路径,直接用了绝对路径,解包时就可能覆盖到系统现有文件。保险起见,收到陌生 tar 包时,先用tar tzf看一下包里路径,确认安全再解压。
6.2 gzip、zip:什么时候用哪个
tar 负责打包,gzip 则负责把单个文件压缩得更小。它们最常用的组合是 tar 加z参数,一步到位。但 gzip 本身也可以单独用:
gzip access.log # 压缩成 access.log.gz,原文件消失 gzip -k access.log # 保留原文件,只生成压缩副本 gzip -d access.log.gz # 解压回 access.log注意,gzip 一次只能处理一个文件,不能把多个文件压成一个。这就是为什么必须配合 tar 使用的原因:tar 先把多个文件打包成一个,gzip 再对这个包压缩。
zip 则是另一种思路。它本身就是打包+压缩一体,在 Windows 和 Linux 之间互传文件时最常用,因为 Windows 带自带 zip 支持,拿过去直接双击就能解压。
zip -r myarchive.zip /home/user/data unzip myarchive.zip -d /tmp/unpacked-r参数不能漏,否则目录下的子目录不会被递归加进压缩包。解压时同样建议用-d指定目录,避免文件散落一地。
讲到压缩包,顺便说一个我踩过的坑:不要用zip去压缩一个特别大的目录,然后指望它的压缩率和 tar+gzip 一样。在纯 Linux 环境里,tar czf的压缩效率通常比 zip 高,而且 zip 处理超多文件时更容易出现文件数溢出或者权限变更的问题。如果追求极致压缩率,还可以用tar cJf,这个J用的是 xz,压缩率比 z 更高,但耗时也成倍增加。大文件备份用 xz,日常传输用 gzip,这个规律基本通用。
最后聊一个我自己的习惯:打包前先用 find 看一眼目录里是否有临时文件、缓存文件或者过大的核心转储文件。如果存在,先把它们清掉或单独排除,打包的体积能小很多。tar 支持--exclude参数,比如:
tar czf backup.tar.gz --exclude='*.log' --exclude='/tmp/cache' /home/user/data注意--exclude要放在源目录前面,这个顺序写错的话,排除规则不会生效,我已经不止一次看到同事在这个小细节上翻车了。文件操作命令学到这一步,已经足够应付日常的查看、查找、对比、权限设置、打包压缩这些高频需求。剩下的就是把这些命令在真实场景里反复使用,熟悉到形成肌肉记忆。真到了需要处理几千个文件、排查复杂日志的时候,你会感谢自己曾经好好练过这些“笨功夫”。