Linux操作命令体系:文件、文本、进程诊断与脚本化

发布时间:2026/11/6 5:47:18
Linux操作命令体系:文件、文本、进程诊断与脚本化 1. 先把命令行当成一门操作系统方言来学很多人第一次接触 Linux 操作命令是在一台只能敲键盘、没有图形界面的机器上。屏幕上只有一个光标在闪输入ls回车出来一串文件名然后就开始背cd是切目录mkdir是建目录rm是删文件。背了三十条一上真机还是懵。问题不在于命令太多而在于没搞明白这些命令背后共用的一套语言规则。Linux 操作命令本质上是一门方言它的语法极其简单——命令名 选项 参数三件套词根也少得可怜大量命令成簇出现ls/lsof/lsblkps/pstree/pgrep动词和名词的组合方式高度一致。你把规则摸透看似零散的上百条命令会自己归位。这篇东西写给两类人一类是刚上手服务器、被Permission denied和command not found折腾得没脾气的朋友另一类是用了几年命令但一直是复制粘贴型选手、想系统补齐底层理解的同学。我不会按字母表罗列命令手册那种内容你man一下就有我要讲的是这些命令在真实场景里怎么组合、为什么这么组合、哪里会出事。全文会围绕文件操作、文本处理、进程与端口诊断、脚本化四个高频区展开每个区都配可复现的操作和踩过的坑。看完你至少能做到拿到一台陌生的机器知道先敲什么、看什么、怎么定位问题。1.1 一切皆文件理解这条铁律之后命令就少背一半Linux 操作命令之所以能收敛得这么好根源是一切皆文件这个设计。普通文本是文件目录是文件硬件设备是文件内核状态也是文件。带来的直接结果是读写、查看、复制、权限这套操作可以通用。举几个具体例子你就能感觉到这套设计的省力之处想看机器有几核 CPU不用装任何工具cat /proc/cpuinfo就行因为 CPU 信息被暴露成了文件。想看内存用量cat /proc/meminfo。想临时静音声卡、调节音量操作的是/dev/snd/下的设备节点。磁盘本身是/dev/sda、/dev/nvme0n1对它做dd就是对整块盘读写。甚至连进程都能当文件看进程号 1234 的信息在/proc/1234/下面cat /proc/1234/cmdline就能看到它的启动命令。理解这一点之后很多专用命令你会发现只是通用命令 特定路径的封装。lsblk输出的信息你在/sys/block/下也能翻到free读的就是/proc/meminfo。具体怎么用这条规律来减少记忆负担我常用的思路是遇到一个不熟悉的资源先去/proc和/sys里找对应目录找到之后用cat、grep、awk直接处理往往比学一个新命令还快。比如排查某个进程打开了多少文件描述符# 进程 1234 当前打开的文件描述符数量 ls /proc/1234/fd | wc -l # 该进程的文件描述符上限 cat /proc/1234/limits | grep open files这两行不需要lsof在极度精简的容器镜像里很多镜像根本不带lsof特别好使。我第一次在某个 Alpine 基础镜像里发现lsof: not found的时候就是靠/proc把问题绕过去的。注意/proc和/sys里的大量文件是只读的虚拟文件echo进去可能没反应也可能直接改变内核行为。比如往/proc/sys/下的文件写值属于运行时调参重启即失效生产机上不要随手试。1.2 命令的运行链路PATH、别名、内建命令与外部命令敲下ls回车之后发生了什么这件事搞清楚能解决掉一大半为什么我的命令行为和教程不一样的困惑。大致流程是这样的shell 拿到你输入的整行先做分词和展开变量替换、通配符展开、命令替换然后判断这是不是 shell 内建命令cd、echo、export、alias都是内建的如果是就直接在 shell 进程内执行如果不是就到PATH环境变量列出的目录里从左到右找同名可执行文件找到第一个就执行后面的不再找。由此衍生出几个非常实用的排查动作# 看一个命令到底走的是哪个路径 type -a ls # 只看可执行文件路径 which python3 # 看 PATH 的完整内容注意顺序就是优先级 echo $PATH | tr : \ntype -a比which更值得推荐因为它会告诉你这是别名、这是内建命令、这是 /usr/bin 下的文件信息更全。我遇到过最典型的一个坑某台机器上ls被加了一个别名alias lsls --colorauto -F某人写脚本时依赖ls的输出做解析结果在交互式 shell 里跑得好好的放进crontab就出错——因为 crontab 的 shell 不加载~/.bashrc别名不生效输出格式变了解析全崩。再说PATH顺序。默认一般是/usr/local/bin:/usr/bin:/bin这种形态/usr/local/bin排在前面这就是为什么自己编译安装的软件会覆盖系统自带的同名命令。如果你装了两个版本的同一工具比如两个版本的编译环境which出来的路径和你以为的不一致八成就是PATH顺序问题。临时解决可以直接用绝对路径调用长期解决就把PATH调整写进 shell 配置文件。# 临时把某个目录提到 PATH 最前面只对当前会话有效 export PATH/opt/tools/bin:$PATH补一句内建命令的坑cd、export、alias、umask这些是内建命令它们改变的是当前 shell 进程的状态。所以你在脚本里写cd /some/dir是有效的但如果你写bash -c cd /some/dir那个cd只影响子进程父进程的当前目录纹丝不动。这个区别在写脚本时非常关键很多人第一次写cd之后再执行相对路径命令失败就是这个原因。1.3 按场景而不是按字母表来记命令我的建议是彻底放弃背命令清单这件事改成建立场景索引。真实工作里你遇到的永远是问题不是命令。下面这张表是我自己桌面便签上的版本按我要干什么来组织比任何命令大全都好用。我要做的事首选命令备选/补充找文件在哪findlocate需先建索引在文件内容里找字符串grep -rnrg装了就更快看文件尾部新增tail -ftail -F应对日志轮转看磁盘空间够不够df -hdu -sh *找大户看谁占着 CPUtopps aux --sort-%cpu | head看端口被谁占了ss -lntplsof -i:端口看进程打开的文件lsof -p PIDls /proc/PID/fd批量改文件内容sed -iperl -pi -e正则更强按列统计awkcutsortuniq -c定时执行crontab -esystemd timer表格里的备选列不是摆设。同一个问题在不同机器上最优解不同容器里可能没有lsof老系统上可能没有ss只有netstat最小化安装可能没装locate的数据库。真正熟练的人脑子里存的是一张问题 → 多条路径的网而不是一条命令。这也解释了一个常见现象新手看到别人敲出一长串管道命令会觉得对方记性好。其实不是那串命令是现场拼出来的每一段解决一个小问题靠管道串起来。第 5 节我们会专门讲管道怎么拼。2. 文件与目录操作最容易出事的一块文件操作是所有 Linux 操作命令里使用频率最高、同时也是破坏力最大的一块。ls敲错顶多没输出rm敲错可能就没了。这一节我把常用命令里真正有用的那部分参数挑出来讲不讲全量选项只讲现场用得上和容易翻车的。2.1 ls、cd、find把找东西这件事做扎实ls大家都熟但有三个参数我认为必须形成肌肉记忆ls -lh # 人类可读的大小排查磁盘占用第一眼 ls -la # 显示隐藏文件很多配置问题就藏在点文件里 ls -lt # 按修改时间倒序找最近谁动过 ls -ltr # 反过来最新的在最下面配合长输出看更顺ls -lt这个组合我用得极多。某次线上配置突然失效第一步就是ls -lt /etc/某服务目录/一眼看到某个配置文件十分钟前被改过问题范围瞬间从玄学收窄到某次变更。cd本身没什么好说的但有两个小技巧值得养成习惯cd - # 回到上一个目录两个目录之间来回跳 cd # 不带参数直接回当前用户的家目录find是文件操作里信息量最大的一个命令也最容易被写成抄来的一段看不懂的咒语。我把它的结构拆开find 起点 条件 动作。三部分各管一件事读的时候按块读。# 在 /var/log 下找 7 天内修改过、大于 100M 的 .log 文件 find /var/log -type f -name *.log -mtime -7 -size 100M # 找出所有权限是 777 的文件安全检查常用 find /data -type f -perm 0777 # 找出 30 天前的文件并删除删除前务必先只打印验证 find /data/tmp -type f -mtime 30 -print find /data/tmp -type f -mtime 30 -delete这里必须强调一条铁律任何带-delete或-exec rm的find第一次执行时一定要把动作换成-print先看一遍结果。我见过不止一次有人把-mtime 30写成-mtime -30符号一颠倒删的是最近 30 天的文件性质完全反了。-print那一步只花你三秒钟。时间参数这块也值得单独说清楚因为它是误删重灾区参数含义记忆方法-mtime -77 天以内修改过负号像最近-mtime 77 天以前修改过正号像以前-mmin -3030 分钟以内m 换成分钟-newer file.txt比 file.txt 更新的拿一个基准文件对比-newer这个参数实际很好用。比如你怀疑某次发布之后系统状态发生了变化拿发布时留下的一个时间戳文件当基准find /etc -newer /tmp/deploy_stamp就能把所有在那之后被改动的配置列出来。2.2 rm、cp、mv 的三条保命习惯rm的危险性不需要解释但光说小心点没用得有具体机制。我自己固定执行三条第一条rm -rf之前先ls同一个路径。这不是形式主义。路径拼错是最高频的误删原因/data/app/logs少打一个s就变成/data/app/log可能是个完全不同的东西。先ls一遍眼睛确认输出的是你想删的东西再按上方向键改命令。第二条变量一定要加引号且用${VAR}形式。这条在脚本里是硬性的# 危险写法 rm -rf $TARGET_DIR/ # 相对安全的写法 rm -rf ${TARGET_DIR:?}/${VAR:?}的含义是如果变量为空或未定义直接报错退出不执行后面的命令。少了这个保护一旦变量因为某种原因没赋值rm -rf $TARGET_DIR/会变成rm -rf /后果不用多说。这是 shell 里少数几个真正能救命的语法糖。第三条重要的删除操作走移动而非删除。在生产机上我的习惯是建一个/data/.trash/目录删除时用mv移进去定期清理。多占一点磁盘换回来的是后悔药。# 一个极简的回收站函数加进 ~/.bashrc trash() { mkdir -p /data/.trash mv -v $ /data/.trash/$(date %s)_$(basename $1); }cp和mv的坑主要集中在两点。一是覆盖行为cp默认直接覆盖不提示mv也一样。想有提示就加-i或者在~/.bashrc里加alias cpcp -i、alias mvmv -i、alias rmrm -i。二是跨文件系统移动同一个文件系统内mv是改目录项瞬间完成跨文件系统时mv实际上是复制 删除大文件会很慢中途中断可能留下半个文件。所以几百 G 的数据迁移我不会用mv而是rsync -a --remove-source-files可断点续传、可校验、可限速。rsync -av --progress --remove-source-files /data/old/ /data/new/-a保留了权限、时间戳、软链接等属性--remove-source-files在传输成功后才删源文件比mv稳得多。2.3 chmod、chown、umask权限数字是怎么算出来的权限这块很多人是靠查表活着的777 是啥、755 是啥、644 是啥。查表当然能干活但一旦遇到umask或者为什么我新建的文件是 664 不是 644这种问题就卡住了。其实规则非常简单。权限分三组属主u、属组g、其他o。每组三个位读r4、写w2、执行x1。三个位相加就是一个八进制数字。所以7 421 rwx6 42 rw-5 41 r-x4 r--0 ---chmod 755 file就是属主 rwx、属组 r-x、其他 r-x。这个算法熟了你就不需要查表了。chmod 644 config.yml # 普通配置文件属主可写其他人只读 chmod 755 run.sh # 脚本属主可执行其他人可读可执行 chmod 600 id_key # 私钥类文件只有属主能读写 chmod 700 /data/private # 私有目录其他人连进都进不去chown改属主和属组格式chown 用户:组 文件配合-R递归chown -R www-data:www-data /var/www/site有个细节容易忽略chown -R递归时如果目录下有软链接默认是不跟随的改的是链接本身。要跟随就用-h的反面——注意 GNU 版本下chown默认行为是不跟随软链接而chmod -R遇到软链接也不跟随。这个在部署时踩过一次一批软链接指向的文件权限没被改到服务启动时读不了排查了半天才发现是递归没跟过去。umask是默认权限的掩码这个概念最容易绕晕。规则是新建文件的默认权限 666 - umask新建目录的默认权限 777 - umask。umask # 查看当前值通常是 022 或 002 umask 022 # 设置文件 644目录 755 umask 077 # 设置文件 600目录 700私有最严格为什么文件基数是 666 而不是 777因为新建文件默认不给执行位——防止你写了个文本文件结果因为权限配置被当程序执行了。这个设计是刻意的。有了这个公式为什么我新建的文件是 664就能自己算出来umask是002某些发行版对用户组内的成员更宽松666 - 002 664组内成员也能写。这个值在多人协作的共享目录里是合适的但在生产环境里往往过于宽松值得检查一遍。3. 文本处理三剑客日志排查的主力服务器上没有 IDE没有图形化的日志查看器你能依赖的就是grep、sed、awk加上sort、uniq、wc这几个小工具。这一节围绕一次完整的线上问题排查来讲而不是逐个讲语法。3.1 grep先缩小范围再谈分析排查日志的第一原则是永远不要直接cat一个几百兆的日志。先缩小范围。grep就是干这个的。grep -c ERROR app.log # 先数一下有多少条 grep -n ERROR app.log | head -20 # 带行号看前 20 条 grep -rn NullPointer /var/log/app/ # 递归搜目录带行号 grep -v DEBUG app.log # 反向匹配排除噪音 grep -i timeout app.log # 忽略大小写 grep -A 5 -B 5 ERROR app.log # 前后各带 5 行上下文-A/-B/-C这三个参数是排查日志的关键。异常栈是一段一段的只匹配到Exception那一行等于什么都没看到必须把上下文带出来。-C 10表示前后各 10 行一般够用。几个实战组合值得记住# 找出所有非 200 状态码的请求统计各状态码分布 grep -oE status[0-9]{3} access.log | sort | uniq -c | sort -rn # 找出某个时间窗口内的错误 grep 2024-05-20 1[0-2]: app.log | grep ERROR # 排除健康检查和使用内部探针的请求看真实流量 grep -v -E (health|ready|metrics) access.log | grep 500 第二条里1[0-2]是正则的字符区间表示 10 点、11 点、12 点三个小时。这个技巧在处理按小时切分的日志时特别顺手。注意正则里的[在 shell 中有通配符含义虽然放在引号里就安全但更稳妥的习惯是所有正则都加单引号。grep [0-9] file永远比grep [0-9] file安全后者在当前目录恰好有名为9的文件时会展开成文件名。还有一个实践中的取舍如果机器上装了ripgrep命令名rg在大量文件中递归搜索时优先用它速度快一个数量级而且默认遵守.gitignore。但要注意它的默认行为差异——rg默认跳过隐藏文件和二进制文件的匹配方式与grep -r不同脚本里依赖固定输出时还是用grep更可控。3.2 sed 与 awk 的分工边界这两个工具经常被混用其实分工很清楚sed是流编辑器擅长按行做替换、删除、插入输入输出都是文本流不擅长结构化计算。awk是文本处理语言擅长按列取字段、做条件判断、求和计数。判断该用哪个看你的动作是不是跟列有关。只改字符串内容用sed要按字段统计用awk。sed最常用的就三类操作# 1. 替换把文件里的 old 换成 new加 g 表示每行全部替换 sed -i s/old/new/g config.txt # 2. 打印指定行范围 sed -n 100,200p app.log # 3. 删除包含某关键字的行 sed -i /^#/d config.txt # 删掉注释行sed -i的坑必须说它直接修改原文件没有备份。GNU sed 支持-i.bak先备份再改我强烈建议养成这个习惯sed -i.bak s/8080/9090/g app.conf一个真实翻车案例有人写sed -i s/old/new/g *当前目录里有个软链接指向生产配置sed跟随软链接把生产文件改了。所以sed -i通配全目录这种操作先ls *看一遍目录内容或者加--follow-symlinks的反面默认不跟随但不同实现有差异别赌。替换内容里含/的时候s/old/new/会语法错乱比如替换路径。解法是换分隔符sed允许用任意字符做分隔sed -i s#/usr/local/old#/usr/local/new#g app.confawk的核心是awk 模式 { 动作 }默认按空白分列$1是第一列$NF是最后一列。# 取第 1 列和第 3 列 awk {print $1, $3} data.txt # 按指定分隔符冒号取第 7 列去重 awk -F: {print $7} /etc/passwd | sort -u # 条件过滤 求和统计 500 错误的总字节数 awk $9 500 {sum $10} END {print sum} access.log # 按某列分组统计 awk {count[$1]} END {for (k in count) print k, count[k]} access.log-F:这个写法要熟因为/etc/passwd、/etc/group、以及大量日志格式都是冒号或特定字符分隔的。$NF表示最后一列$(NF-1)是倒数第二列——处理字段数不固定的行时特别好用。3.3 一次完整的日志排查链路实录光讲单个命令没意义我把一次真实的排查过程还原一遍。场景是某项目接口在凌晨出现间歇性超时需要从日志定位原因。第一步确定问题时间窗口。先看错误总数和分布判断是持续性还是尖峰式。grep -c timeout app.log grep timeout app.log | awk {print substr($1,1,13)} | sort | uniq -c这里substr($1,1,13)取时间戳前 13 位形如2024-05-20 10把秒级时间聚合成小时uniq -c出来的一列数字就是每小时的超时数。尖峰在哪一小时一目了然。第二步看尖峰时段的具体请求。缩小到那一小时把耗时的和超时的都捞出来。grep 2024-05-20 03: app.log | grep -E (timeout|cost[0-9]{4,}) | head -50cost[0-9]{4,}匹配耗时四位数以上毫秒计即 1 秒以上的请求。-E开启扩展正则|表示或。第三步找出慢请求的共同特征。假设日志格式里第 7 列是接口路径。grep 2024-05-20 03: app.log | awk $10 1000 {print $7} | sort | uniq -c | sort -rn | head这一条把3 点这一小时内、耗时字段大于 1000 的请求的接口路径提取出来按出现次数倒排。前三名就是重点怀疑对象。第四步看这几个接口当时的调用量和并发。grep 2024-05-20 03: app.log | grep /api/order/query | wc -l如果这个接口在这一小时的调用量是平时的十倍基本可以确定是流量突增如果调用量正常但慢请求集中那更像外部依赖数据库、下游服务在抖动。往下查就是看同机其他日志或者用第 4 节的进程诊断命令看系统负载。这套流程的价值在于每一步都在收缩范围全量日志 → 一小时 → 一类错误 → 一个接口 → 一个数量级判断。全程没有cat整个文件都是在流上操作几百兆的日志也是秒级响应。心得日志字段顺序一定要先搞清楚再写awk。我踩过的坑是把耗时字段的列号记错白白分析了半小时才发现统计的是一列无关的数字。先用head -3 app.log加awk {for(i1;iNF;i) print i, $i}把每列标出来比凭记忆靠谱。4. 进程、资源与端口现场诊断文件操作是日常进程和端口诊断才是救火。这类命令的特点是有明确的检查顺序乱敲会浪费时间。我给自己定的顺序是先看整体负载再看具体进程最后看进程在等什么。4.1 ps、top、pidstat从全局到单个进程top是入口但它的默认交互体验对新手不友好。先用几个一次性命令把宏观情况摸清楚。uptime # 输出10:30:00 up 30 days, 2:10, 3 users, load average: 1.20, 0.85, 0.60load average三个数字分别是 1 分钟、5 分钟、15 分钟的平均负载。判断标准是拿它和 CPU 核数比4 核机器负载 4 大约是满载长期超过 4 就说明有排队。看趋势比看绝对值重要——从右往左看如果是递减的15 分钟 5 分钟 1 分钟说明负载在回落如果是递增的说明问题正在发生。# 按 CPU 倒序取前 10 ps aux --sort-%cpu | head -11 # 按内存倒序 ps aux --sort-%rss | head -11 # 看进程树理清父子关系 ps -ef --forest | grep -A 5 某服务名--sort这个参数我强烈推荐比ps aux | sort -k3 -rn更直观字段名直接写不用记列号。ps aux输出里%CPU是第 3 列、%MEM是第 4 列、RSS实际物理内存是第 6 列但不同实现有差异用--sort一劳永逸。进程树在实际排查中价值很高。如果某个主进程下面挂了 50 个子进程在抢占 CPU说明是主进程放得太开。pidstat不在最小化安装里需要装sysstat包但它的价值完全值这个麻烦——它给的是时间区间内的增量,而不是top那种采样瞬间。这两个信息的含义区别很大。# 每 2 秒采样一次共 5 次看某个进程的 CPU 和内存 pidstat -p 1234 2 5 # 看所有进程的磁盘 IO找出读写大户 pidstat -d 2 5top里显示的%CPU是自上而下的采样估算某些短时突发的高占用会被平滑掉pidstat在区间内累加更能反映真实消耗。有一次排查某个进程看起来 CPU 不高但机器整体负载很高就是靠pidstat -d发现它在疯狂做磁盘 IO——CPU 全在iowait上进程本身的用户态 CPU 确实不高。这也引出一个基本判断看 CPU 高之前先看是真算还是干等。top里%wawait和%ididle两个值很关键。如果%wa持续很高说明 CPU 在等磁盘如果%id高但负载也高那可能是大量进程在等锁或者 IO。4.2 lsof 与端口占用排查端口被占用是开发环境最高频的问题之一。标准解法是三步# 1. 看端口被谁占了 ss -lntp | grep 8080 # 2. 如果输出里有 PID直接看这个进程 ps -fp 1234 # 3. 确认后决定是杀还是换端口 kill -15 1234 # 先礼 kill -9 1234 # 后兵ss参数解释-l只看监听状态-n不做域名解析快-tTCP-p显示进程需要 root 权限才能看到别人的进程。老系统上可能只有netstat对应的写法是netstat -lntp。# 看所有已建立的 TCP 连接及其状态统计 ss -tan | awk NR1 {print $1} | sort | uniq -c | sort -rn这一条的输出能直接看出连接状态分布。ESTAB是正常连接数量TIME-WAIT是主动关闭方留下的CLOSE-WAIT大量堆积则说明有一方没正确处理关闭——这通常是应用层代码的问题不是网络问题。lsof的信息维度更宽它能列出进程打开的一切文件、目录、网络连接、设备。lsof -p 1234 # 这个进程打开了啥 lsof -i:8080 # 谁在用 8080 端口 lsof D /data/uploads # 哪些进程在用这个目录递归很慢 lsof /data/app.log # 谁占着这个文件最后一个用法解决过一个经典问题日志文件被删了但磁盘空间没释放。原因是某个进程还持有文件句柄df显示磁盘满du加起来却对不上。用lsof | grep deleted能找出这类幽灵文件处理方法是重启对应进程或者用 /proc/PID/fd/N清空。4.3 磁盘与内存df、du、free 的正确打开方式磁盘排查有一条经验公式df看总量du找大户lsof抓幽灵。df -h # 各分区使用率 du -sh /data/* 2/dev/null | sort -rh | head -10 # /data 下前 10 大目录 du -ah /data/logs | sort -rh | head -20 # 深入到文件级du -sh加2/dev/null是为了屏蔽权限不足的报错噪音很有必要——不然你在根目录跑一次屏幕会被刷满。free有个新手最常问的问题为什么 available 和 free 差那么多因为 Linux 会把空闲内存拿去做缓存buff/cache这是设计如此不是内存泄漏。真正该看的是available那一列——它表示还能分给新应用的内存是包含可回收缓存的。free接近 0 但available充足是完全正常的。free -h # 重点看 available 这一列如果真要清缓存一般不建议除了压测场景需要冷启动环境sync echo 3 /proc/sys/vm/drop_caches这条命令会丢弃页缓存、目录项和 inode 缓存执行后系统短期内会变慢要重新从磁盘读。生产机上不要为了让内存数字好看去执行缓存本来就是花内存换性能。5. 把命令串起来管道、重定向与脚本单个命令只是零件真正的生产力来自组合。这一节讲管道和重定向里那些看着简单但细节很多的地方以及怎么把一串常用命令固化成一个能复用的脚本。5.1 管道与重定向几个必须精确的细节管道|把前一个命令的标准输出接到后一个命令的标准输入。重定向、、2、、则是把三个标准流stdin0stdout1stderr2接到文件上。最容易出错的是和的区别以及 stderr 的处理cmd out.log 21 # 标准输出和错误都写进 out.log覆盖 cmd out.log 21 # 追加 cmd out.log # bash 的简写等价于上面第一行 cmd 2/dev/null # 丢弃错误输出21的顺序有讲究。cmd out.log 21是对的先把 stdout 指向文件再让 stderr 复制 stdout 的指向。如果写成cmd 21 out.logstderr 会复制当时的 stdout终端然后再把 stdout 指向文件——结果就是错误还是打在屏幕上。这个顺序问题我在别人的脚本里见过太多次。还有一个非常实用的重定向技巧同时输出到屏幕和文件。cmd 21 | tee out.log cmd 21 | tee -a out.log # 追加模式tee命令得名于水管的三通接头非常形象。做耗时任务时用它既能看到实时进度又留下完整记录。有个隐蔽的危险它会在命令执行之前就清空目标文件。所以cat file file会得到空文件因为打开的一瞬间原内容就没了。安全写法是用临时文件中转或者用spongemoreutils 包提供cat file | sponge file管道还有个默认行为值得知道管道中的每个命令都在子 shell 里运行。这意味着在管道里改变量出了管道就没了count0 echo a b c | while read -r x; do count$((count1)); done echo $count # 输出 0不是 3这是新手写脚本最经典的坑之一。解法是避免在管道里累积变量或者用进程替换while ... done (command)或者让整个 pipe 在同一个 shell 里执行。5.2 shell 脚本骨架与 set 三件套把命令固化下来的第一步是写脚本。我的脚本开头固定这几行#!/usr/bin/env bash set -euo pipefail IFS$\n\t这四行不是仪式每一行都在挡一类事故set -e任何命令返回非零就立即退出。不加这个脚本中间失败了会继续往下跑可能造成更严重的后果。set -u使用未定义变量时报错退出。这条是防rm -rf $DIR/里DIR没赋值那种灾难的。set -o pipefail管道中任一环节失败整个管道就返回失败。不加的话管道的退出码只看最后一个命令grep xxx nofile | wc -l会返回 0因为你只判断了wc。IFS$\n\t把字段分隔符设为换行和 Tab避免文件名带空格时被拆成多个词。最后一条在批处理文件时是必须的# 危险文件名有空格就废 for f in $(ls *.log); do echo $f; done # 安全用 glob 直接展开 for f in *.log; do echo $f; done # 更安全含子目录 while IFS read -r -d f; do echo $f; done (find . -name *.log -print0)-print0配合read -d 是处理任意文件名包括含空格、换行的标准姿势。这个组合看着别扭但它是唯一不会出错的方式。一个完整的实用脚本骨架长这样#!/usr/bin/env bash set -euo pipefail LOG_DIR/data/logs RETENTION_DAYS7 usage() { echo 用法: $0 [-d 日志目录] [-n 保留天数] exit 1 } while getopts d:n:h opt; do case $opt in d) LOG_DIR$OPTARG ;; n) RETENTION_DAYS$OPTARG ;; *) usage ;; esac done [[ -d $LOG_DIR ]] || { echo 目录不存在: $LOG_DIR 2; exit 1; } echo 清理 $LOG_DIR 下 $RETENTION_DAYS 天前的日志... find $LOG_DIR -type f -name *.log -mtime $RETENTION_DAYS -print -delete注意-print -delete的顺序先打印再删除删除的每一条都会显示出来事后有据可查。5.3 crontab定时任务的三个隐形坑crontab -e编辑当前用户的定时任务格式是五段加命令分 时 日 月 周 命令 0 3 * * * /opt/scripts/clean.sh /data/logs/clean.log 21第一个坑环境变量不继承。crontab执行时用的是极简环境PATH通常只有/usr/bin:/bin你在~/.bashrc里配的所有东西都不生效。所以脚本里最好全用绝对路径或在 crontab 顶部显式定义PATH/usr/local/bin:/usr/bin:/bin SHELL/bin/bash第二个坑工作目录不确定。默认在用户家目录不是脚本所在目录。脚本里依赖相对路径必挂。所以脚本里要么用绝对路径要么开头先cd到目标目录。第三个坑输出没地方去。cron 执行失败会尝试发邮件但大多数机器没配邮件服务报错就静默消失了。所以每条任务都加上 日志 21出问题能查。还有一点容易忽略%在 crontab 里是特殊字符表示换行如果命令里有date %Y%m%d必须转义成\%0 3 * * * /opt/scripts/backup.sh $(date \%Y\%m\%d) /data/logs/bak.log 21这个坑不踩一次很难记住。想绕开它可以把这个逻辑挪进脚本内部crontab 里只写0 3 * * * /opt/scripts/backup.sh干净得多。6. 常见问题速查与踩坑清单这一节是纯粹的经验汇总都是我在实际环境里反复遇到的东西。6.1 报错速查表报错信息常见原因排查动作command not found没装 / 不在 PATH / 拼错type -a 命令echo $PATHPermission denied缺执行位 / 缺读权限 / 目录无 x 位ls -l看权限检查每级目录No such file or directory文件明明存在脚本的 #! 解释器路径错了head -1 script.shfile script.shText file busy文件正在被运行lsof 文件先停进程再覆盖Argument list too long通配符展开的文件太多改find ... -exec或xargsToo many open files文件描述符上限到了ulimit -ncat /proc/PID/limitsNo space left on device磁盘满 / inode 满df -hdf -ilsof | grep deletedCannot allocate memory内存或 overcommit 限制free -hdmesg | tailAddress already in use端口被占ss -lntp | grep 端口Bad interpreter脚本在 Windows 下编辑过行尾是 CRLFdos2unix或sed -i s/\r//倒数第二条的df -i值得单独提一句。磁盘空间没满但写不进去很可能是 inode 用完了——大量小文件场景邮件队列、缓存目录、session 文件特别容易碰到。df -h显示有空间df -i显示 100%那就得去清小文件删大文件没用。倒数第一条也是高频问题脚本在本地编辑器写好、传到服务器上就跑不了报bad interpreter。原因是行尾符是 CRLF 而不是 LF。用file script.sh一看输出里带with CRLF line terminators就确诊了。6.2 我自己的几条铁律第一破坏性命令先降级执行。rm换成lsfind -delete换成-printsed -i换成不带-i先看输出。三秒钟的成本换的是不可逆操作的安全边际。这个习惯我保持了七八年中间救回来至少四五次。第二执行前先确认自己在哪、以谁的身份。有一组命令我几乎形成了条件反射whoami pwd hostname看起来啰嗦但多开几个终端窗口的时候你真的会搞不清这个窗口连的是测试机还是生产机。尤其是用同一套终端配色的时候。我现在的做法是给不同环境的 shell 提示符配不同颜色PS1里带上主机名视觉上就不会混。第三任何一次改一批的操作先拿一个样本试。批量替换配置文件、批量改权限、批量重命名先对单个文件执行验证输出正确再上全量。# 先对一个文件试 sed s/old/new/g config-01.conf | diff config-01.conf - # 确认 diff 输出符合预期再批量 find . -name config-*.conf -exec sed -i.bak s/old/new/g {} | diff 原文件 -这个用法很好用-表示标准输入能直接对比改完的样子和现在的样子比人眼扫一遍可靠得多。第四多用man和--help但别指望从头读到尾。man页面的正确用法是搜索进去之后按/输关键字n跳下一个。比如查find的时间参数直接man find然后/mtime比在网上搜一堆互相抄的文章快且准。第五命令的历史记录是你的资产。CtrlR反向搜索历史命令配合history | grep 关键字比重新敲一遍快得多。我还会定期把一些复杂的组合命令记到一个~/notes/cmd.md里按场景分类。真正高频的那二三十条用着用着就刻进肌肉记忆了剩下的查笔记就行——没必要追求全部记住那是背单词的思路不是用工具的思路。最后分享一个我最近才养成的小习惯任何在终端里一次敲超过三行的复杂操作我都会先在/tmp下建个.sh文件写清楚再执行。一是能改二是留下记录三是下次直接复用。终端里一口气敲完看似帅但出了错要整段重来还不容易复盘。慢一点稳一点实际上更快。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询