
很多人学Shell都是从一张“常用命令清单”开始的cd、ls、df、grep、find背得滚瓜烂熟可真到了排查问题或者写部署脚本的时候还是觉得命令不听使唤。这不是记性不好而是你只是在背命令的拼写没有理解它们在实际场景里是怎么配合、怎么取值、怎么被解析的。这篇东西不打算把命令大全再抄一遍而是挑几个我这些年被问得最多、也最容易被用错的点——cd和ls里的隐藏习惯、df和du怎么看才不踩坑、shift在脚本里到底在移位什么、脚本执行方式为什么能影响环境变量——拆开来聊清楚。适合刚接触Shell命令的人打底子也适合已经写了几个月脚本但一直有点“知其然不知其所以然”的朋友查漏补缺。1. 先从最基础的cd和ls说起导航命令的深层用法1.1 cd不只是切换目录更是一套导航习惯cd大概是所有人学会的第一条Shell命令但说实话大部分人都只是停在“cd /xxx/xxx”这个层面。我见过不少同事在目录之间来回跳先pwd看一下当前位置再cd到目标目录搞错了又cd回来鼠标和键盘来回切换效率低得离谱。其实cd自带几个特别好用的快捷方式只要你愿意养成习惯目录切换可以是完全不用动脑的肌肉记忆。先说最实用的cd -。它的作用是切换到上一次所在目录相当于一个“返回”键。想象一下这个场景你在修改 /etc/nginx/conf.d 下面的站点配置改完要跑到 /var/log/nginx 下去看日志验证效果。如果不加思考你可能会先cd到日志目录看完再手动输入长长的绝对路径回去。但如果你用 cd -上一次的目录会自动接上来回切换就是两条命令的事。这个操作在深度嵌套的目录里尤其救命省去的不只是打字时间还有你反复确认路径的注意力。cd ~和直接输cd都能回到当前用户的家目录这个大家都知道但很少有人会用pushd和popd。这两个命令维护了一个目录栈能让临时去别的目录看一眼之后再回到原位。比如我正在写一个脚本写到一半突然想去确认某个配置文件的路径我只需要pushd /etc看完之后popd就回到脚本目录了。多个目录来回切的时候pushd 和 popd 比 cd - 更稳因为它不是只记上一个而是把整条来路都压进了栈里想回哪一层就回哪一层。如果你经常在固定的几个项目目录之间切换还可以利用环境变量CDPATH。你把这个变量设成几个常用目录的父路径之后不管在哪个位置只要输入cd 项目名Shell就会自动去CDPATH里找这个目录。不过我自己平时不太用CDPATH因为它的行为比较“玄学”一旦有同名目录它可能会把你带到意料之外的地方。相比之下我更推荐给高频目录起别名比如在 .bashrc 里写一行alias projcd ~/work/my-project输入成本低行为还特别直观。1.2 ls的显示技巧与“分屏”真正含义ls 看起来就是个“列出文件”的命令但热搜里出现“ls分屏显示”这个词说明很多人其实卡在了同一个问题上目录里文件太多一屏刷过去根本看不完。实际上这里有两条路可以走一条是让ls的输出“翻页”另一条是让多个ls输出“并列”很多人把这两个需求混在一起了。如果你只是觉得一个目录的文件多到刷屏最简单的方式是加管道ls -l | less。这样ls的输出会交给less分页显示按空格翻页、按q退出所有内容都能慢慢看。这个技巧在 /usr/bin 这类动辄几千个文件的目录里特别管用。如果想让输出更紧凑还可以用ls -1强制每个文件占一行配合 less 使用体验非常舒服。如果你说的“分屏”是指想在同一个终端窗口里同时看两个目录的内容那就不是ls本身该干的活了得靠终端分屏工具。最常见的是Tmux。比如你想对照开发目录和部署目录的文件结构先跑tmux new -s work开一个会话然后按Ctrlb %把窗口垂直切成两半左右两个面板里分别cd到不同目录再执行ls这样两边内容都在眼前对照效率极高。Tmux还能让面板里的内容独立滚动一个看日志一个编辑文件互不干扰。再说说ls排序的几个实用参数。ls -lt按修改时间排序最新的文件在最上面日常找刚改过的文件特别方便ls -lS按文件大小排序排查大文件的时候用得上ls -lh把文件大小显示成K、M、G这种人类可读单位而不是赤裸裸的字节数这个基本是必加的。建议你干脆在.bashrc里把常用组合直接写成别名比如alias llls -lFh alias lals -lAh alias lllls -lt这里有个小坑要提醒一下ls在终端里默认会用颜色区分文件类型这本身是好事但当你把ls的输出重定向到文件或者用管道传给grep的时候颜色转义序列可能会混进去导致输出里出现一堆类似\033[01;34m的乱码。遇到这种诡异情况不要慌直接ls --colornever关掉颜色就行了。我现在只要是写脚本里调用ls的场景都会主动加 --colornever免得在管道处理上翻车。2. 查看系统状态df、du、free这些“体检命令”怎么读2.1 df命令磁盘占用看什么、怎么避开坑搜索热度里专门有“shell df命令”说明大家日常排查磁盘满了的频率很高。df的基本用法其实就一条df -h-h表示human把字节数换算成G、M这种看得懂的单位。但只看这一条远远不够很多人吃了大亏都是从“df -h看着很正常”开始的。df有几个参数是必须记熟的。df -T会多显示一列文件系统类型能帮你分辨当前这个挂载点是ext4、xfs还是网络文件系统。df -i查看inode使用情况这个太重要了。Linux文件系统里除了要有数据块存文件内容还得有inode记录文件元数据如果你创建了大量小文件比如日志系统生成了几百万个几K的小日志就能把inode全部消耗完。这时候你用df -h看还有很大剩余空间但touch新文件会直接报No space left on device特别迷惑。我接过一次真实的故障某服务的日志目录里堆了大概800万个几KB的小文件导致inode占用100%。当时所有人都在盯磁盘容量df -h显示用了不到30%折腾了半天才意识到问题出在inode上。后来再遇到“空间够但写不进”的诡异现象我的排查顺序已经固定成先df -h看容量再df -i看inode两个都正常才去查别的。这个经验你最好提前记住别等踩坑了再想起来。df还有一个小知识容易被忽略ext4文件系统默认会为root用户保留5%左右的空间普通用户用到95%就会报错而root还能继续写。所以在某些极端情况下普通用户写不进去但用root去检查空间会发现一切正常。这不是系统坏了只是“保留空间”策略在起作用。如果你明确知道磁盘不会用满可以通过tune2fs -m 1 /dev/sdX把保留比例调低但我不建议大家没事就去改这个保留空间在磁盘碎片整理和紧急恢复时是有价值的。另外看df的时候可以配合watch做实时监控比如watch -n 1 df -h每秒刷新一次适合在灌数据或者跑批处理的时候观察磁盘变化。这个组合我经常在压测环境里用比一遍遍手输df方便太多了。2.2 df和du口径不一致到底谁在说谎磁盘分区明明没有大文件但df显示用了80%这种“鬼打墙”同样堪称经典。要理解这个问题你得知道df和du统计的其实不是同一个维度。du统计的是目录树里看得见的文件占用的块数df统计的是整个文件系统总空间和剩余空间之间的差值范围是整个分区不是某个目录。最常见的矛盾是某个文件被进程打开了后来又被rm删除了但进程还一直持有它的文件句柄。du在扫描目录的时候这个文件已经不在目录树里自然不统计它但df看的是文件系统层面这个被删除文件的存储块还没被真正释放所以空间依旧被占着。时间一长这种孤儿文件累积多了df和du的差距就越拉越大磁盘看着满了可你翻遍所有目录也找不到罪魁祸首。排查这个问题有现成的命令lsof L1它会列出所有已被删除但仍被进程打开的文件。看到输出的那一瞬间你通常会恍然大悟噢原来是个日志文件被活跃进程占着。解决方式也很简单重启一下对应进程或者只是让那个进程解除对这个文件的引用空间马上就能释放回来。这个经验我建议你写进自己的排查清单里比默认“磁盘坏了”或者“有病毒”靠谱得多。看内存的时候free命令也有类似的“坑”。free -h会显示total、used、free、shared、buff/cache和available这几列。很多刚学的人一看used特别高、free特别低就觉得内存不够了其实不是。buff/cache是Linux用来缓存文件系统数据的应用需要内存时随时可以回收而available那一列才是真正“有多少内存可以被新进程使用”的真实指标。只要available够大你完全不用焦虑used有多高。反过来如果available很低而且持续走低再配合swap占用上升那才是真的内存吃紧该查查是哪个进程在吞内存了。顺便说一句网上有人教你用echo 3 /proc/sys/vm/drop_caches手动清缓存让free看着好看。这种做法在绝大部分场景下都是没必要的甚至可能因为清除了页缓存而短暂降低系统性能。别为了“数字好看”去动内核参数这不是运维的正常操作。3. 写脚本之前必须搞懂的几个点shift、位置参数和脚本执行方式3.1 shift命令位置参数的高级玩法shift在热搜里被单独点出来说明很多人学到位置参数之后就开始卡壳了。其实shift的逻辑特别简单把位置参数往左挪一位。原来$1的位置变成之前的$2$2变成之前的$3以此类推同时$#的数值也会减一。如果写成 shift 2那就是一次性挪两位。它存在的意义是让你可以在脚本里“逐项消费”参数配合循环和判断做命令行参数解析。我写一个稍微真实点的例子。假设你要写一个备份脚本支持-s指定源目录、-d指定目标目录、-k指定保留份数典型的写法就是#!/bin/bash src dst keep7 while [ $# -gt 0 ]; do case $1 in -s) src$2 shift 2 ;; -d) dst$2 shift 2 ;; -k) keep$2 shift 2 ;; *) echo 未知参数: $1 shift 1 ;; esac done echo 源目录: $src echo 目标目录: $dst echo 保留份数: $keep这里的核心就是shift。你每处理完一个参数就把参数列表整体左移这样下一次循环里$1会自动变成下一个参数。如果不写shift会怎样$1永远是-s循环永远跳不出去脚本直接卡死。很多初学者初学者写参数解析时犯的最大错误就是忘了移动参数指针然后陷入死循环。除了解析参数shift在不知道参数总数、只打算先处理头几个参数的时候也好用。比如你可以shift 2跳过脚本名加一个固定参数然后剩下的记得统一$处理。顺便讲一个相关但经常被搞混的点$和$*的区别。不加引号的时候两者看起来一样加上引号之后$会把每个参数当成独立的词而$*会把所有参数拼成一个字符串。写脚本时我几乎永远用$因为逐个参数处理才是你要的语义除非你明确要一个合并后的串比如拿参数拼一个命令的日志描述这才会用到$*。3.2 脚本执行方式bash、source和可执行权限的区别“shell脚本执行linux命令”这个热搜词点出了一个实质脚本本质上就是把一批命令组合在一起按顺序执行而执行方式会直接影响这些命令能不能正确地改到你的环境。常见的三种执行方式行为差异很大我分清楚它们之后脚本出的诡异问题起码少了一半。第一种是bash script.sh这是直接调用bash去解释执行脚本文件。这种方式不需要脚本有可执行权限也不依赖文件开头的#!/bin/bash那行注释因为你已经明确指定了解释器。第二种是./script.sh要求文件有执行权限并且系统会去看第一行的shebang决定用谁来解释。第三种是source script.sh也有人写成. script.sh它是在当前Shell进程里执行脚本内容相当于把脚本一行一行粘贴到当前终端来跑。为什么这个区分那么重要因为子Shell的环境变化不会带回到父Shell。用 bash 和 ./ 方式跑脚本脚本里执行的任何export或cd都是作用于子Shell的跑完就消失了。但 source 是在当前Shell里直接执行所以脚本里的环境变量修改、目录切换都会保留下来。最典型的例子是Python虚拟环境的激活source venv/bin/activate之后你的当前终端才能识别python命令一旦有人用bash activate去跑你会发现什么都没发生还以为虚拟环境坏了。这类脚本天生设计成被source的你说用bash跑当然没效果。还有一个容易踩的坑Windows下编辑过的脚本文件往往带 \r 换行符拿到Linux上执行时会报bad interpreter: /bin/bash^M这种错因为Linux把\r当成了文件名的一部分。解决办法很简单sed -i s/\r$// script.sh把每行结尾的 \r 删掉就行。我现在从Windows传脚本到服务器都会先看一眼有没有这个问题免得发出去一堆问号。如果脚本本身是给某台机器写配置的或者想让它按固定逻辑批量跑命令那就在脚本开头写成#!/bin/bash set -euo pipefail-e表示只要一条命令出错就退出-u表示变量未定义就报错-o pipefail表示管道中任何一段失败都算失败。这三个组合是我写脚本的默认开头能让脚本从“一路执行到错”变成“出错立即知道在哪”排起错来舒服太多了。4. 日常排查与避坑遇到这些“鬼问题”怎么定位4.1 引号、空格、通配符脚本里最容易翻车的地方Shell命令写多了你会发现最坑你的不是那些晦涩难懂的参数而是几个看起来人畜无害的基础细节。引号必须算一个。Shell对变量做的是“分词”处理也就是说一个变量即使内容是一整段话不包引号的话它很可能被当成多个独立参数。最经典的翻车现场是处理文件名。假设你写了个filenamemy report.txt然后执行cat $filenameShell会把空壳踢掉变成你实际执行的是cat my report.txt也就是试图cat两个文件my 和 report.txt。一旦文件名带空格几乎必炸。正确的写法永远是cat $filename记住一个原则所有变量引用只要你不确定内容里会不会有空格就一律加双引号。更可怕的情况是变量为空的时候。比如你在脚本里写rm -rf $dir如果dir因为某种原因没被赋值Shell会把命令展开成rm -rf后面没有参数吗问题就在这里——如果配合了其他通配符或者脚本里恰好有别的路径变量被带进去后果不堪设想。所以我非常建议脚本开头加上set -u这样引用未定义变量会直接报错不会默默展开成一个空字符串至少能拦住一半的“手滑”。另一个容易翻车的是for循环遍历“行”的场景。很多人想逐行处理文件里的内容会写for line in $(cat file)但实际上这个写法是按空白字符空格、Tab、换行分割的不是按行分割。假如文件里有一行 “Hello World”for会拆成两个词分别处理。要真正按行遍历应该配合while和readwhile IFS read -r line; do echo $line done file.txt这里的IFS和-r也很关键前者防止read修整行首行尾的空格后者禁止对反斜杠做转义。写一遍彻底搞清楚比在项目里被bug追着打强得多。4.2 find、xargs和grep的配合技巧命令单个用谁都会真正考验人的是组合能力。拿找文件这个需求来说find就是最底层的工具。find . -name *.log -mtime 7可以找出当前目录下七天前被修改过的所有log文件这是清理日志的基础操作。-mtime的单位是天7表示超过7天-7表示7天以内。想按大小找的话find / -size 500M能列出所有超过500M的文件排查磁盘占用时特别好使。find有一个常用的“搭档”xargs。find . -name *.log -print0 | xargs -0 rm这串命令的意思是把find找到的文件逐个交给rm去删除。这里的 -print0 和 -0 成对出现表示用空字符而不是换行来分隔文件名这是为了对付文件名里的空格和换行。如果你不用-0遇到一个名字里带空格的文件xargs会在空格处拆开轻则删不掉重则删错文件。这个细节是很多人的知识盲区我建议直接形成肌肉记忆find的结果交给xargs处理时永远用 -print0 配 -0。grep在处理文件内容搜索的时候也别老想着grep -r。虽然grep -r 关键字 /目录确实可以递归搜索但如果你只想搜某类文件更好的做法是先用find把范围缩小再交给grepfind src/ -name *.py -exec grep -Hn TODO {} \;-exec和结尾的\;会让find对每个匹配文件执行一次grep优点是简单直接缺点是文件多时效率不太行。如果文件数量很大建议还是用xargs -0批量处理效率会高不少。再分享一个日志分析里特别好用的组合统计某个日志里访问来源IP的排行。指令是tail -n 10000 access.log | awk {print $1} | sort | uniq -c | sort -rn | head -20这条管道把连接日志翻出来提取第一列IP排序去重并计数再按数量倒序排最后取前20名。每一段的输出都明确可读就算中途出问题你也可以在管道中间截断查看排查思路会清晰很多。这种“管道思维”才是Shell命令的高阶用法——每个命令像积木一样搭起来就是一套完整的小工具。5. 让终端更好用的延伸思路从命令效率到桌面体验5.1 终端分屏工具进一步解放ls的显示既然热搜里这么多人搜“ls分屏显示”我干脆把解决方案讲透。除了前面提到的ls | less分页浏览真正的“多目录并列显示”需要用终端分屏工具这个词搜索背后的完整答案其实是Tmux。Tmux是一个终端复用器它的基础用法完全不复杂。tmux new -s work创建一个叫work的会话进入之后按Ctrlb %是垂直分屏按Ctrlb 是水平分屏。每个分屏在Tmux里叫panel你可以分别在两个panel里cd到不同目录、各自执行ls、各自滚动查看输出。想退出某个panel输入exit就行。如果服务器或本地电脑上还没装Tmux用 .bashrc 里给常用终端模拟器设置的快捷键也能分屏比如GNOME Terminal默认的CtrlShiftT开新标签但论灵活性和可保存的布局Tmux还是首选。Tmux还有一个杀手锏是会话保持。你用SSH连上一台服务器在Tmux里跑了个长时间任务中途网络断了重新连上去之后执行tmux attach -t work之前的所有panel状态都还在那个任务也还在后台继续跑。这个能力让“中途断线”变得不再可怕几乎可以说是我离不开的工具之一。等你习惯了Tmux再回头用裸终端会感觉少了一只手。5.2 一个小延伸GNOME桌面里调一调外观说完了纯命令行的东西再聊点轻松的。如果你的日常桌面是Linux而且用的是GNOME环境可能搜命令的时候会看到“blur my shell”这个东西。它其实不是Shell命令而是一个GNOME扩展作用是给顶栏、概览界面和程序坞加上统一的毛玻璃模糊效果让桌面看起来更精致一点。安装方法很常规可以通过系统自带的“扩展管理器”或者在GNOME的官方扩展站点找到它安装后在“优化”工具里可以调模糊半径、亮度这些参数。我的个人建议是这类视觉增强工具适合在命令使用已经比较顺手、并且你确实在意桌面颜值的时候再折腾。它不会让命令行更快也不改变任何Shell行为纯属锦上添花。而且扩展装多了之后GNOME升级时偶尔会遇到扩展冲突导致桌面异常处理起来比命令行问题更麻烦。所以精力分配上一定要分清楚主次先把命令和脚本组合练扎实再考虑美化。说到底Shell命令的学习没有太多捷径但绝对有正确的路径。我自己的习惯是每隔几个月就把常用的命令组合整理一遍写成一个私人笔记放进配置仓库。换新机器的时候把环境配置和这些笔记恢复回来半小时就能回到得心应手的状态。你现在看到的这篇文章其实就是这个习惯的衍生品。真的把“cd -”“ls -lht”“df -i”“shift”“source”这些细节刻进日常操作里你回头看刚学Shell时的自己会觉得效率差了不止一个量级。