
拿到一台不熟悉的Linux服务器我先不看业务文档也不问前任运维第一件事永远是登录Shell把系统状态摸一遍。这不是过度谨慎而是被坑出来的习惯——早几年我接手过一台持续报警的机器业务方说“没什么大事就是偶尔卡一下”结果上去一看跑了两年的Java进程占满了内存磁盘日志已经堆积到98%连ssh登录都差点超时。那天我花了大半个晚上靠的就是一条条Shell命令从系统信息查到进程状态从磁盘使用查到日志轮转最后才把问题压下去。这篇是Shell核心基础命令的下一篇重点聊系统与权限操作。上一篇解决了文件操作、文本处理、管道重定向这些基本功这一篇要解决两个更关键的问题这台机器现在处于什么状态我能不能在这个状态下安全地完成我的操作内容主要面向已经把Shell基础命令用得比较熟、但也想系统补齐系统管理和权限控制这块短板的读者也包括写过一些简单脚本、想进一步理解后台进程和用户体系的开发者。先说清楚范围。这里讲的系统操作指的是系统信息查看、进程管理和服务控制三类权限操作则覆盖用户管理、文件权限位、提权机制和文件属性锁。两个方向合在一起基本就是日常运维和开发排障时最常用的那批命令。每一节我会说明这条命令解决什么问题、关键参数怎么用、实际场景中容易踩哪些坑而不是单纯把man手册抄一遍。1. 系统信息采集先摸清家底再动手1.1 uname、hostname与uptime确认你操作的是什么机器拿到一台服务器第一件事永远是确认自己在哪、跑的是什么系统。我见过有人把CentOS的运维习惯直接套到Ubuntu上结果systemctl和service之间来回切换绕了大半天也见过开发同学在一台只用于编译的机器上跑了关停命令差点把别人的测试环境带崩。这些问题的根源很简单没有先确认系统类型和主机身份就急着执行后续操作。最基本的三个命令我每次登录都习惯顺手敲一遍# 查看内核版本和系统架构 uname -a # 查看主机名 hostname # 查看系统运行时长和负载 uptimeuname -a会一次性输出内核名称、主机名、内核版本、硬件架构。不同发行版之间内核版本和包管理方式差异很大。比如CentOS 7系列很多指令在CentOS 8/RHEL 8上表现不同Ubuntu则更激进地推新版本内核。如果看到内核版本是5.x和4.x你的排障思路就得调整。hostname在排查多机协作问题时很关键尤其是在微服务部署和集群环境中。有时候ssh到一台机器Host key验证报错第一反应就是检查hostname和IP是不是对得上别是连错机器了。uptime除了告诉你机器开机多久更重要的是后半部分的load average三个数字分别代表过去1分钟、5分钟、15分钟的平均负载。这个数字不是CPU使用率而是处于可运行状态和不可中断状态的任务数平均值。单看负载本身没有意义要结合CPU核数判断。比如8核机器负载15已经明显偏高说明任务排队严重2核机器负载15那几乎等于卡死状态。还有一条更实用的组合命令hostnamectl在支持systemd的系统上hostnamectl会一次性输出发行版名称、内核版本、操作系统版本、主机名、机器ID等一串信息。比uname和cat /etc/os-release分开敲要方便不少。封装脚本时也推荐优先hostnamectl输出格式更稳定便于二次提取。1.2 free、df与du内存与磁盘排查的标准动作系统卡顿、服务异常、程序启动失败十次里有八次和内存或磁盘有关。排查的第一步就是看free和df。# 查看内存使用情况-h以人类可读单位显示 free -h # 查看磁盘分区占用 df -h # 查看指定目录大小 du -sh /var/logfree -h的输出重点不在一眼看到的used那一列而在第二行的buff/cache。Linux会把空闲内存用作文件缓存尽可能加速磁盘读写所以used看着很高其实很多是缓存在应用需要时是可以回收的。真正需要警惕的是available这一列它表示在不触发swap的情况下还可以分配给应用的内存。我见过不少新人把free的输出当成内存泄漏的铁证看完used数值就直接kill进程。正确做法是观察available是否持续走低同时配合top或ps定位具体进程不要急着下结论。df -h排查磁盘满这个场景就更常见了。有一个经典坑文件被进程打开后即使rm删除磁盘空间也不会释放因为文件句柄还被进程占用。df显示磁盘满但用du定位不到大文件这种情况十有八九就是这个原因。解决方案是用lsof查看被删除但未释放的文件# 列出已删除但仍在使用的文件 lsof | grep deleted找到对应的进程重启或让进程重新加载磁盘空间才会真正释放。这个话题很多运维老手都吃过亏新人提前知道了能省掉一晚上的排查时间。du经常和df混着用。df是看分区整体du是看目录实际占用。排查磁盘哪来的大文件思路是从根目录逐层往下缩小范围du -h --max-depth1 / 2/dev/null | sort -hr | head -202/dev/null是为了把权限不足报错的噪音过滤掉sort -hr则按容量从大到小排序一眼就能看出该往哪个目录深挖。2. 进程管理让后台程序乖乖听话2.1 ps与top先锁定占用资源的元凶排查问题不要凭感觉猜进程用ps和top拿到精确数据再动手。ps的两种常用写法各有侧重# 查看所有进程的完整命令行BSD风格 ps aux # 查看所有进程带父进程信息System V风格 ps -ef很多教程会告诉你这两条命令差不多实际上关键区别在显示格式。ps aux的%CPU和%MEM是当前瞬时占用ps -ef里PPID定位父子关系更方便。实际操作中我没少两头切先用ps aux按CPU排序定位“谁在吃资源”再用ps -ef确认它的父进程是谁判断是正常服务还是被某个脚本拉起来的孤儿进程。用pgrep按进程名快速查找也很常用# 精确匹配进程名输出PID pgrep -x mysqld # 模糊匹配 pgrep -f java.*jarpgrep -f配合正则匹配整条命令行在排查多进程场景时是神器。比如一台机器上跑了三四个Java应用光靠进程名分不清谁是谁用pgrep -f匹配启动参数里的jar包名就非常精准。top属于动态监控。进入top后按P按内存排序按C看完整命令行按1看每个核的状态。这些交互快捷键一定要记住不然你只能看到一堆默认数据价值大打折扣。生产环境排查时我习惯先开top观察两三秒确认是持续飙高还是间歇性峰值这能很大程度上区分资源泄漏和短时任务。2.2 kill与killall结束进程的正确姿势很多人对kill有一个误解kill就是强制杀进程。实际上kill默认发送的是SIGTERM信号编号15含义是“请正常退出”。真正强制的是kill -9发送SIGKILL信号内核直接回收进程资源不给任何善后机会。正确的结束流程应该是# 1. 先发SIGTERM让进程自己处理退出逻辑 kill -15 PID # 2. 过几秒检查是否还在 ps -p PID # 3. 确实没退再考虑SIGKILL kill -9 PID很多守护进程在收到SIGTERM时会做一些收尾工作比如释放锁、写日志、关闭数据库连接。直接kill -9会把进程打死但可能留下数据库锁、临时文件或者不完整的写盘记录。线上实例跑Kafka或MySQL的话这里要格外慎重kill -9造成的故障往往比进程卡住本身还严重。killall按进程名结束适合一次处理多个同名进程。但它也遵守信号规则killall nginx默认发送SIGTERM。想看到底匹配了哪些进程先跑killall -l或pgrep确认范围再动手。Shell里还有个经常被忽视的前后台配合命令CtrlZ挂起jobs查看作业bg放到后台fg调回前台。调试脚本时很有用。比如脚本执行到一半发现太慢了先CtrlZ挂起查一下CPU和IO再决定是bg丢后台还是fg继续。2.3 nohup与systemctl让程序稳定地在后台跑写好脚本程序后最常遇到的需求就是让它脱离终端关掉ssh窗口也能继续运行。这时候nohup 是最快方案nohup python server.py server.log 21 nohup的作用是让进程忽略SIGHUP挂断信号终端断开时进程不会随之退出。 server.log 21把标准输出和标准错误都重定向到日志文件避免进程输出无处安放导致异常。最后的把整个命令放到后台执行并返回PID方便后续管理。这个组合看着简单实际操作中有几个容易遗漏的细节。第一日志文件如果一直不轮转跑几天就能占满磁盘第二nohup进程不受systemd管理重启机器后不会自动拉起第三如果进程本身需要监听端口记得确认端口没被占用。相比之下systemctl是更规范的服务管理方式# 服务管理三件套 systemctl start 服务名 systemctl status 服务名 systemctl enable 服务名 # 确认服务开启自启 systemctl is-enabled 服务名systemd这一套体系已经把服务托管、开机自启、崩溃拉起、日志采集都标准化了。如果你的程序需要长期稳定跑强烈推荐写一个简单的.service单元文件而不是图省事丢个nohup就完事。单元文件核心内容[Service] ExecStart/usr/bin/python /opt/app/server.py Restartalways RestartSec5 UserappuserRestartalways配合RestartSec5能让进程在崩溃后5秒自动重启实现简单的守护效果。3. 用户管理从建号到提权的完整流程3.1 useradd与passwd规范地创建系统账号创建普通用户看似简单实际上不少团队的服务器账号管理处于“谁都能跑”的状态。规范的做法是业务进程不用root跑单独建一个属于应用的用户权限边界要明确。# 创建用户指定家目录和默认Shell useradd -m -s /bin/bash deploy # 设置或重置密码 passwd deploy # 查看用户信息 id deployuseradd的-m参数是“创建家目录和初始化配置文件”很多人建完用户发现没有/home/deploy目录就是因为漏了这个参数。不过有些发行版默认useradd就会建家目录Ubuntu就是这种策略所以具体发行版行为还要实测确认。如果只创建系统账号不需要登录能力也不想要家目录用-M参数useradd -M -s /usr/sbin/nologin appuser这样的账号只能用服务方式运行程序无法通过ssh登录攻击面会小很多。数据库、Web服务、缓存服务都应该用这类专用账号运行。usermod用于修改已有用户属性# 附加到sudo组 usermod -aG sudo deploy # 修改用户默认Shell usermod -s /bin/bash deploy # 锁定/解锁账号 usermod -L deploy usermod -U deploy这里要特别注意-aG和直接-G的区别。-G不加-a会覆盖原有附加组列表你原本想把用户加进docker组结果把所有既有用户组都挤掉了严重时连sudo权限都丢了。修改组操作时务必先gid列表确认再执行。3.2 chmod与chown把文件权限位搞得明明白白Linux权限的核心是权限位读r(4)、写w(2)、执行x(1)三组分别对应属主、属组、其他人。数字表示法是这三个数字的加权和。# 属主rwx、属组r-x、其他人r-x chmod 755 script.sh # 属主rw-、属组r--、其他人--- chmod 640 config.conf # 递归修改目录 chmod -R 750 /opt/app755和750这两个值在运维场景里出现频率最高。755适合普通可执行文件既能运行又允许其他人读取750适合目录或脚本其他人连读的权限都不给安全边界更清晰。有个细节容易被忽略目录的执行权限和文件不一样。目录的x权限表示“能否进入目录”r权限表示“能否列出目录内容”。如果目录只有r没有x你能看到文件名列表但访问不了内容和元数据操作会很别扭。chown修改属主和属组# 修改属主 chown deploy /opt/app # 修改属主和属组 chown deploy:deploy /opt/app # 递归修改 chown -R deploy:deploy /opt/appchown操作一定要清楚一个原则普通用户只能把文件权限往小改只有root能任意改属主。如果你收到“Operation not permitted”的报错多半就是在chown上越权了。3.3 sudo与visudo在受控范围内获得临时权限sudo的核心理念是“授予有限特权”而不是让人随便切换到root。# 以root身份执行单条命令 sudo systemctl restart nginx # 切换到root Shell sudo -i # 查看当前用户被授予的sudo权限 sudo -lsudo -i和su -的区别在于前者认证的是当前用户的密码后者需要输入root的密码。sudo在设计上就是为了避免分发root密码管理工作由管理员在/etc/sudoers里定义。修改/etc/sudoers务必用visudo而不是直接用vim编辑。visudo会做语法检查改坏了还能拦截避免因为格式错误导致整个sudo不可用。最常见的sudoers配置# 允许deploy用户执行所有命令 deploy ALL(ALL) ALL # 限制只允许重启nginx deploy ALL(root) /usr/bin/systemctl restart nginx实际工作中尽量把sudo权限收敛到最小范围。所有人都是ALL ALL的话sudo就失去了它的隔离意义。3.4 chattr与umask进阶权限控制chattr是Linux文件系统的属性锁能在权限位之上再加一层更硬的保护。# 给文件加不可变更属性即使是root也不能随意删除修改 chattr i /etc/nginx/nginx.conf # 限制只能追加内容适合日志文件 chattr a /var/log/app/access.log # 移除属性锁 chattr -i /etc/nginx/nginx.confi和a的效果很直观前者是“锁死文件”后者是“只允许追加”。给关键配置文件加i之后哪怕有人手滑执行rm也会被系统拒绝。umask则决定了新建文件和目录的默认权限。它的逻辑是用系统默认的权限最大值文件是666目录是777减去umask的值得到实际创建权限。# 查看当前umask umask # 临时设置umask为027 umask 027umask 027表示新建文件的默认权限是640目录是750属组可读执行其他人完全不可访问。如果你不希望你创建的脚本被同服务器上的其他人随便读取就该把umask调高到027或077。4. 文件系统与磁盘挂载、查找与打包整理4.1 mount与umount手动挂载存储设备云服务器加了块数据盘或者手里有个U盘要临时拷文件都得用mount手动挂载。# 查看当前挂载情况 mount | grep -E ^/dev # 查看块设备信息 lsblk # 挂载分区到目录 mount /dev/sdb1 /data # 卸载 umount /datamount前最好先lsblk确认设备名。我遇到过一台机器上sdb和sdc接反了的情况——买的是数据盘结果系统盘识别成了sdb。不先lsblk确认就盲目mount很可能把系统盘覆盖掉。稳妥做法是结合blkid看分区的UUID和文件系统类型再操作。卸载时如果提示“target is busy”说明这个目录正被进程占用。用fuser或lsof确认并结束占用进程后再卸载fuser -km /dataumount经常被拼写成umount实际命令是umount初学者容易在这上面卡一下。4.2 find与tar定位文件与打包备份find的作用是“按条件搜索文件”它比ls -R效率高太多尤其是大目录树场景# 按名称查找 find /opt -name *.log # 按大小查找文件大于500M find / -type f -size 500M # 按修改时间查找3天前修改的 find /var/log -type f -mtime 3 # 结合-exec批量操作 find /tmp -name *.tmp -exec rm {} \;find -exec是双刃剑。它批量威力强但一旦条件写错后果可能无法挽回。我曾经见过一条find / -perm -777 -exec chmod 644 {} ;把系统里所有特殊权限文件都改成了普通权限直接导致几个关键程序启动失败。建议操作前先用不带-exec的查询跑一遍确认匹配的文件都在预期范围内再正式执行。tar依旧是打包压缩的首选# 打包并压缩 tar -czvf backup.tar.gz /opt/app # 只打包不压缩 tar -cvf backup.tar /opt/app # 解包到指定目录 tar -xzvf backup.tar.gz -C /tmp/restore-czvf里的z是gzip压缩如果文件本来已经很大压缩耗时和CPU占用都不可小觑。对超大目录先考虑裸tar再考虑zstd等更快的压缩算法。4.3 ln与rename链接文件与重命名技巧ln创建链接是Shell使用里一个常被忽视却很好用的点# 创建软链接相当于Windows的快捷方式 ln -s /opt/app/current version-2024.12 /opt/app/current # 创建硬链接内容和原文件共享同一inode ln /var/log/nginx/access.log /tmp/access_hard.log软链接指向路径路径失效链接就断硬链接指向inode只在同一文件系统内有效。部署场景里经常用软链接做个“当前版本”的指向发布新版本时只要改软链接指向即可停服务时间和回滚成本都能压得很低。重命名文件命令行下最常用的是mvmv old_name.txt new_name.txt很多人忽略了mv在跨文件系统时实际上是“复制删除”如果源和目标是两个分区大文件重命名会非常慢。所以同一目录或同一分区内重命名用mv跨目录但同分区的场景也放心用跨分区的场景则要考虑rsync或cp。5. 服务、网络与系统日志组合拳排查问题5.1 systemctl与journalctl用systemd管起服务全家现代Linux发行版上systemd已经成了事实标准的服务管理器。systemctl不只是开关服务它还能查看依赖、查看自启状态、重载配置等。# 查看服务状态关注Active和Main PID字段 systemctl status sshd # 查看所有运行中的服务 systemctl list-units --typeservice --staterunning # 修改配置文件后重载不需要重启服务 systemctl daemon-reload systemctl restart 服务名重载配置不发散改了service文件先daemon-reload让systemd重新读配置再restart服务。直接restart有时会用旧的Unit配置白白浪费一次重启。journalctl是systemd生态里的日志利器# 查看某服务的实时日志 journalctl -u 服务名 -f # 查看最近1小时日志 journalctl --since 1 hour ago # 按优先级过滤error及以上 journalctl -p err传统syslog把日志写到/var/log/messages或/var/log/syslogjournalctl则集中管理系统日志。服务名-filter其实比查文本日志方便太多配合-f实时跟踪排障效率提升一个量级。5.2 网络连接检查ping、ss与curl组合使用网络问题排查最常用的是想确认“通不通”和“服务在不在”。# 测到目标服务器的连通性 ping -c 4 192.168.1.10 # 查看本机监听端口 ss -tlnp # 发起HTTP请求观察返回状态 curl -I http://localhost:8080/healthss -tlnp这条命令比netstat -tlnp更值得养成习惯ss属于iproute2包输出速度更快状态更准确。排查端口占用时-p能看到占用进程的PID和名称非常关键。curl -I看HTTP响应头是检查Web服务是否正常的标配。返回码200说明服务存活502/503则多半是后端网关或应用本身的问题。健康检查接口确实在服务端做好响应curl排查会更高效。5.3 系统日志与dmesg找内核和硬件层面的线索应用问题看journalctl内核和硬件问题就要看dmesg了。# 查看内核环形缓冲区信息 dmesg | tail -50 # 看是否有OOM、磁盘错误、硬件报错 dmesg | grep -iE oom|error|faildmesg里最值得关注的是OOM Kill记录。如果应用进程莫名其妙消失第一反应就是看dmesg有没有出现Out of memory。很多内存泄漏的案例最后都是在dmesg里找到确凿证据的。6. 常见问题与排查技巧实录6.1 权限拒绝Permission denied的几种可能Permission denied是Shell里最常见的报错之一但很多人不知道它至少有三种完全不同的场景报错场景原因解决方案直接执行脚本报Permission denied脚本没有x执行权限chmod x script.sh读取文件报Permission denied文件或父目录权限不足chmod/chown调整属主权限写入文件报Permission denied当前用户无写权限或文件系统只读确认属主和数据盘挂载参数写脚本时最容易忘记的一件事Shell脚本必须要有执行权限才能直接运行。用sh script.sh或bash script.sh可以绕过执行位限制直接解释执行但正式环境更推荐赋权后直接运行这样能兼容脚本本身通过shebang选择解释器也方便进程管理。6.2 Shell脚本中的常见坑shift、for循环与变量扩展写Shell脚本入门的门槛不高但坑是真不少。我总结了几个高频问题# shift命令处理位置参数时逐左移 while [ $# -gt 0 ]; do echo 处理参数: $1 shift doneshift每执行一次位置参数就向左移动一位$2变成$1原来的$1被丢弃。写参数解析脚本时shift和case组合非常常用但它容易让人犯迷糊的是shift后$#也会同步变化循环条件依赖参数个数时容易提前退出。for循环的坑更多# 遍历文件列表注意文件名带空格时别用for直接匹配 for f in *.log; do echo 处理文件: $f done如果一个目录下的文件名包含空格for直接按空格切分f可能变成半个文件名。遇到这种情况用find配合while read处理更安全find /var/log -name *.log -print0 | while IFS read -r -d f; do echo 文件: $f done变量扩展也是个高频踩坑点。引用变量时一定要加双引号不然变量的值里含空格就会被Shell拆成多个参数。“$”在脚本里处理多参数时尤为重要不加双引号带空格参数会被拆散。6.3 误操作恢复rm删错文件之后的紧急措施这个我真不想写但踩过太多次坑还是得说。rm删错文件后的第一原则停止写入立即停止任何可能往磁盘写入数据的操作。如果文件还在被某个进程占用用lsof找到进程copy出文件描述符指向的内容还能抢救# 找到删掉的日志文件对应的进程 lsof | grep deleted # 尝试从/proc恢复 ls -l /proc/PID/fd/文件描述符不过这个方案依赖进程还持有文件句柄一旦进程也停了基本就无从恢复。最好的防线永远是备份和快照。生产环境重要目录用rsync定时同步一份到备份服务器云服务器的话打开磁盘快照功能改配置前手动打一次快照。习惯越早养成损失越少。6.4 环境变量与PATH脚本执行找不到命令的根源很多人写脚本时会遇到“crontab里能跑手动跑就报错”的诡异问题这类问题十有八九是环境变量差异导致的。手动登录Shell会加载.bashrc、.profile把PATH设置好cron运行脚本时是non-login shell默认PATH路径很窄/usr/local/bin常常不在其中。排查方法非常简单在脚本开头打印环境变量#!/bin/bash echo PATH$PATH /tmp/debug_env.log通用解法是脚本开头明确设置PATH#!/bin/bash export PATH/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin这个习惯看着不起眼但能解决掉大量“脚本在服务里跑不起来”的偶发问题。到这里Shell的系统与权限操作核心命令基本都覆盖了。最后分享一个我自己的习惯每次新装一台机器我会先敲一遍系统信息采集那组命令确认内核版本、磁盘挂载和服务状态然后看一眼sudo权限配置再开始部署业务。这个过程不到两分钟但能避免后面一大半的权限和兼容性问题。实际使用中你会发现Shell能力提升最快的路径不是记住每个参数而是把命令串起来解决具体问题磁盘告警了知道用df找根因进程失联了会用ps和dmesg定位权限报错了能熟练排查属主和模式位。把这些日常场景反复操练Shell对你来说就不再是一条条孤立的命令而是一套能随时组合的思维工具。