ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

登录即见:用Bash脚本让Linux系统状态自动呈现

登录即见:用Bash脚本让Linux系统状态自动呈现 1. 为什么要在登录 Shell 时查看系统信息1.1 登录即见的系统体检省下的不只是几秒钟做运维和技术支持这几年我最深的一个体会是每次登录服务器都要下意识敲一遍uptime、free -h、df -h再把内核、负载、内存、磁盘挨个看一遍时间长了真的累。特别是手上同时维护几十台机器的时候哪怕每台就多花二十秒一轮巡检下来就是半小时还容易看漏。有了登录时自动输出系统信息的脚本这一切就变得轻松多了。只要你 SSH 登录或者在本地打开一个终端就能在敲正式命令之前先看到当前机器的基本健康状况。这个“登录即体检”的习惯能让你在故障发生时第一眼就注意到异常比如磁盘快满了、负载突然飙高、内存耗尽只剩几百 MB你根本不用等到用户来投诉自己登录的那一刻就能发现苗头。这个方案适合谁我觉得只要是每天都在和 Linux 打交道的人都适合。新手可以用它来熟悉系统状态类命令老手可以用它当巡检辅助工具。更重要的它不是某个收费监控软件的替代品而是一段完全可控、可以随时改的 Bash 脚本想加一条就加一条想改成中文提示也很简单。1.2 为什么选择 Bash 脚本而不是监控平台常见的服务器监控方式有很多开源的 Prometheus、Zabbix云平台自带的监控面板都能做到图形化展示系统指标。但它们都存在同一个问题数据反映的是“过去几秒钟的采样结果”而且你往往需要打开另一个页面才能看到。对于登录这个入口来说Bash 脚本是距离最近、最不打扰人的方案。Bash 脚本的另一个优势是几乎零依赖。Linux 系统只要活着就一定有 Bash 或者可兼容的 Shell而我们要用到的uname、free、df、uptime这些命令都是 coreutils 和 procps 包自带的属于系统根基不像 Python 脚本那样还需要考虑解释器版本和第三方库。哪怕你用的是精简版 Docker 容器只要里面装了 bash这个思路基本都能复用。还有一个很现实的原因定制成本极低。监控平台想加一条“显示当前在线用户数”你得找配置文件、找采集器、改图表用 Bash 脚本就是加一行who | wc -l的事。我自己维护过几套不同的脚本版本线上环境用标准版开发测试机用带上 Git 分支的增强版每个版本都能在五分钟内改完这种灵活性是重型方案给不了的。2. 核心脚本设计与实现2.1 脚本整体结构与信息分类一个好的系统信息脚本不能是一大堆命令的简单堆砌。我在设计时会先把信息分成几个大块每一块对应一类运维关注点这样输出看着也清爽。通常我分为六块系统标识发行版名称、内核版本、主机名、架构运行时间与负载uptime 显示的时间和 1/5/15 分钟平均负载内存情况总量、已用、可用、Swap磁盘情况根分区和其他关键挂载点的总容量、已用百分比网络与会话本机 IP 地址、当前在线用户、最近几分钟的登录失败记录硬件资源CPU 核数、语境切换、最高占用进程这六块信息基本覆盖了日常登录时需要早知道的“有没有问题”和“这里是什么环境”。如果有特殊需求比如判别这台机器是数据库节点还是 Web 节点还可以在脚本里读一个外部的角色标签文件然后显示对应的角色说明这个我后面会展开讲。2.2 每行命令背后的原理与参数选择脚本里最核心的几条命令我逐一解释一下为什么这么写。首先是系统标识部分if [ -r /etc/os-release ]; then OS_NAME$(grep -w ID /etc/os-release | cut -d -f2 | tr -d ) OS_VERSION$(grep -w VERSION_ID /etc/os-release | cut -d -f2 | tr -d ) else OS_NAME$(uname -s) OS_VERSION$(uname -r) fi/etc/os-release是几乎所有现代 Linux 发行版都会提供的文件里面用简洁的 KEYVALUE 格式描述发行版信息。用grep -w匹配ID而不是直接用grep ID是为了避免把ID_LIKE、VERSION_ID这些同样包含 ID 字样的字段也抓进来。cut -d -f2表示按等号切分取第二列tr -d 去掉双引号。老系统没有这个文件时就回退到uname -s和uname -r保证脚本不报错。CPU 核数的获取方式值得说下很多人一开始会写nproc这个命令确实简单但某些容器环境里nproc返回的是宿主机的核心数而不是当前容器可用的配额。我更推荐CPU_CORES$(grep -c ^processor /proc/cpuinfo)/proc/cpuinfo里每个逻辑 CPU 都会有一个processor : N的行统计它的行数就是逻辑核数。这个方案在物理机、虚拟机、容器里都比较符合直觉不容易被配额问题误导。内存和磁盘就相对直白MEM_TOTAL$(free -h | awk /Mem/{print $2}) MEM_USED$(free -h | awk /Mem/{print $3}) MEM_AVAIL$(free -h | awk /Mem/{print $7}) DISK_USAGE$(df -h / | awk NR2{print 根分区: $2 总量, 已用 $3 ( $5 )))free -h会按人类可读的单位输出awk 里面/Mem/匹配内存行$2是总量、$3是已用、$7才是真正可用的数。这里特别注意在较新版本的free输出中available是最有参考价值的字段因为它包含了可回收的 buffer/cache。df -h /只查根分区因为根分区满是最常见的故障其他挂载点可以按需添加。2.3 脚本完整示例附注释下面这个脚本我综合了上面所有思路并加入了灰色分隔线和颜色高亮。颜色不是必须的但在深色终端里确实能提高可读性一会儿我会说明怎么让颜色只在支持时启用。#!/usr/bin/env bash # 登录时显示 Linux 系统信息 # 适用: 所有 bash/sh 可用的 Linux 环境 # 颜色定义只在终端支持时使用 if [ -t 0 ] command -v tput /dev/null 21; then GREEN$(tput setaf 2) YELLOW$(tput setaf 3) RED$(tput setaf 1) BLUE$(tput setaf 4) RESET$(tput sgr0) else GREEN; YELLOW; RED; BLUE; RESET fi echo ${BLUE} 系统信息 ${RESET} # 1. 系统标识 if [ -r /etc/os-release ]; then OS_NAME$(grep -w ID /etc/os-release | cut -d -f2 | tr -d ) OS_VERSION$(grep -w VERSION_ID /etc/os-release | cut -d -f2 | tr -d ) else OS_NAME$(uname -s) OS_VERSION$(uname -r) fi echo ${GREEN}系统发行版:${RESET} ${OS_NAME} ${OS_VERSION} echo ${GREEN}内核版本:${RESET} $(uname -r) echo ${GREEN}主机名:${RESET} $(hostname) echo ${GREEN}系统架构:${RESET} $(uname -m) # 2. 运行时间与负载 load$(cut -d -f1-3 /proc/loadavg) uptime -p | sed s/up // | xargs -I{} echo ${GREEN}运行时长:${RESET} {} echo ${GREEN}平均负载:${RESET} $load # 3. 内存情况 mem_line$(free -h | awk /Mem/{printf %s/%s, 可用 %s, $3, $2, $7}) echo ${GREEN}内存情况:${RESET} $mem_line # 4. 磁盘情况 disk_line$(df -h / | awk NR2{printf 根分区 %s, 已用 %s (%s), 剩余 %s, $2, $3, $5, $4}) echo ${GREEN}磁盘情况:${RESET} $disk_line # 5. 网络与会话 # 只显示非回环的 IPv4 地址 ip -4 addr show scope global 2/dev/null | awk /inet/{print $2} | cut -d/ -f1 \ | tr \n | xargs -I{} echo ${GREEN}IPv4 地址:${RESET} {} online_users$(who | wc -l) echo ${GREEN}当前在线用户:${RESET} $online_users # 6. 硬件资源 cores$(grep -c ^processor /proc/cpuinfo) echo ${GREEN}CPU 逻辑核心:${RESET} $cores echo ${BLUE}${RESET}有一点要注意uptime -p的输出是up 1 hour, 5 minutes所以用sed s/up //去掉前导的 up再交给 xargs 拼到 echo 行里。如果你用的是精简工具集没有xargs也可以直接read一次变量再输出。脚本里我刻意在颜色变量上做了判断如果没有tput或者标准输出不是终端就置空颜色字符串避免生成出一堆^[[1;31m之类的转义垃圾。3. 部署与配置让脚本每次登录自动执行3.1 推荐放置位置/etc/profile.d 与 ~/.bashrc 的取舍脚本写好了放哪里才能“每次登录自动执行”这其实是新手最容易踩坑的地方。Linux 的 Shell 启动脚本有好几个/etc/profile、/etc/profile.d/*.sh、~/.bash_profile、~/.bashrc它们的加载时机和范围不同。我个人的经验是优先放在/etc/profile.d/下文件名取sysinfo.sh。原因是/etc/profile在几乎所有发行版中都会循环加载/etc/profile.d/下的 .sh 文件RHEL/SUSE 系和 Debian 系都是这个约定这样可以做到“一次放置全场生效”包括通过 SSH 登录时的交互式会话。对于只需要当前用户生效的场景再放进~/.bashrc。注意一个细节/etc/profile只对登录 Shelllogin shell加载如果你在桌面上打开一个图形终端有些终端模拟器默认是交互式非登录 Shell它只读~/.bashrc不一定读/etc/profile。所以最好的做法是需要全系统生效就放/etc/profile.d/只想自己看就放~/.bashrc如果在图形终端里没反应优先检查是不是非登录模式导致的。3.2 配置步骤与环境变量兼容实际操作步骤如下把脚本保存为/etc/profile.d/sysinfo.sh如果脚本里涉及敏感信息比如内网 IP权限设为644即可不需要可执行权限因为它是被 source 的如果是独立脚本想要被直接调用则需要chmod x执行一次source /etc/profile或者重新登录验证效果测试不同登录场景SSH 登录、本地 TTY 登录、图形终端打开。环境变量兼容这里多讲一点。脚本中我用了#!/usr/bin/env bash这是为了照顾那些 Bash 不在/bin/bash下的系统。如果某个环境只提供了/usr/bin/bash现在不少发行版都是这样用/bin/bash也会软链过去但env更稳妥。如果你要在 Git BashWindows 下的 Bash 环境里使用这个脚本要特别注意两点。一是free和/proc/loadavg在 Git Bash 中通常不存在或者内容不同脚本会报错建议先用一个条件判断加载该段或者把不支持的模块用command -v守卫掉。二是路径上的/etc/profile.d在 Windows 上可能不存在需要手动创建对应目录或者改用~/.bashrc。这类兼容性处理是脚本工程化中很值得养成的习惯别以为一个脚本写完就完事了能适应多少环境才是考验。3.3 多主机分发与差异化角色配置当你需要把这套脚本同步到几十台机器时手工一份份复制显然不现实。我常用的是一个小循环for host in 10.0.0.1 10.0.0.2 10.0.0.3; do scp sysinfo.sh root$host:/etc/profile.d/ ssh root$host chmod 644 /etc/profile.d/sysinfo.sh ssh root$host source /etc/profile /dev/null 21 echo $host done done如果环境比较复杂建议把脚本放到 Git 仓库里管理再配一个简单的 Ansible playbook 或者纯 SSH 脚本推送。这样后续改版本时不用一台台登录去改还能保留历史记录。不同角色机器如何显示不同信息我用的方案是在脚本里读/etc/sysinfo.role文件这个文件里只需要一行字比如web、db、build。脚本启动时判断角色然后只显示该角色最关心的指标。比如数据库机器重点看内存和 IO那就在角色为 db 时多打印一条vmstat 1 3的采样结果。这个你也可以按需改成从 DNS 反查主机名等灵活度很高。4. 常见问题与排查技巧实录4.1 脚本为什么不执行权限、Shell 类型与父子进程我见过最多的抱怨是“我明明放进 /etc/profile.d 了怎么登录一点反应没有”。这里面有几个反复出现的坑。第一个坑是权限不足。如果你把文件放到全局目录下但没有给其他用户读取权限普通用户登录时 source 它就会报错或者直接跳过。解决办法是chmod 644 /etc/profile.d/sysinfo.sh别给 777安全性太差。第二个坑是 Shell 不是 Bash。部分系统默认sh使用的是 dash 而不是 bashdash 不认[[ ]]、source这些 bash 语法。如果你写的脚本用了#!/usr/bin/env bash但要被/etc/profile用. $file加载时实际执行语法是 dash 的所以脚本里尽量不要用 Bash 专属语法比如[[ ]]、数组、${var,,}这类。我的经验是统一写成 POSIX 兼容的格式用[ ]代替[[ ]]用. file代替source这样在所有 POSIX sh 和 Bash 下都能跑。上面给出的脚本虽然用了 bash 的$()但这个在 POSIX sh 里也支持所以没问题。第三个坑是登录会话类型。你以为每次 SSH 登录都是 login shell其实有些 SSH 服务器配置了ForceCommand或者客户端用ssh host command这种非交互模式登录它不会加载/etc/profile自然也不会执行脚本。这时候可以人为在命令后加bash -l但更好的办法是把要执行的信息脚本放到/etc/ssh/sshrc里它会在 SSH 会话建立时执行。不过在 sshrc 里执行的进程没有 TTY写脚本要注意不能依赖交互操作。4.2 输出乱码与颜色失效如果你的终端打印出^[[32m这种奇怪字符原因是颜色控制码没有被正确解析。常见情况有两个一是终端不支持 ANSI 颜色此时应该在检测逻辑里加上[ -t 1 ]判断标准输出是终端二是你用了echo -e但在不同 Shell 下表现不一致。推荐用tput来生成颜色序列这也是我在脚本里这么写的原因。tput会根据TERM环境变量查询正确的转义序列兼容性远好于写死的\e[32m。如果某个环境没有 tput则退化到无颜色输出保证可读性优先。另外如果你通过某些跳板机的 Web 终端登录可能会发现颜色正常但分隔线对不齐。这是因为中文字符宽度在终端计算中与英文字符不同。简单粗暴的办法是分隔线多用一些等号或者干脆不用线改用类似“ 系统信息 ”这样带文字的标题。别追求像素级完美运维工具首要的是信息准确。4.3 登录后总感觉卡顿性能优化技巧加了这套脚本后有的同事反馈说 SSH 登录变慢了。排查下来发现是某一条命令拖了后腿。最常见的是ip命令在某些环境下要等待网络状态稳定或者你的脚本里主动去做了 DNS 反查、调用了外部 API那自然更慢。我的处理原则是脚本里只允许使用本地瞬时命令任何可能产生网络请求的命令一律不要放进来。如果确实需要获取外部信息给它加一个总超时TIMEOUT_SEC2 ROLE$(timeout $TIMEOUT_SEC cat /etc/sysinfo.role 2/dev/null || echo unknown)timeout命令在 coreutils 里很常见就算系统比较老也能通过timeout 2这样的调用给命令上个保险。另外free、df、uptime、who这些命令本身都很快瓶颈大多在网络和磁盘挂载检查上。如果你df -h挂了 NFS 网络盘它有可能会卡住所以在脚本里应该只检查本地文件系统df -h -x tmpfs -x devtmpfs -x overlay --local--local这个参数能强制只看本地磁盘对于挂载了很多远程存储的机器特别有用。加了它之后登录速度基本回到毫秒级。4.4 兼容性与中文字段处理以及精简环境下的降级方案有段时间我需要在几十台 CentOS 6、Ubuntu 16、SUSE 12 混合环境里跑同一套脚本结果发现几个老系统的命令输出格式和字段名不一样。比如旧版free没有available字段只有buffers和cached旧版df的百分比在$5但某些精简版在$4。一个通用的兼容写法是先探测字段是否存在再决定输出哪一列mem_usage$(free -h) if echo $mem_usage | awk {print $7} | grep -q ^[0-9.]; then mem_avail$(echo $mem_usage | awk /Mem/{print $7}) else mem_avail$(echo $mem_usage | awk /Mem/{print $NF}) fi别看这个判断粗糙但它在老系统和现代系统间都能得到一个合理的“可用内存”值。这种“检测字段是否存在再决定输出策略”的思路比一堆 ifelse 判断版本号要皮实得多因为你不需要维护一个发行版版本清单。还有一个小问题是中文环境。如果你把LANG设置成zh_CN.UTF-8free -h和uptime的输出可能会带中文单位比如“G”不变但某些工具会显示“可用”字样awk 字段位置可能因此错乱。最稳妥的做法是在脚本开头临时固定语言环境export LANGC这样所有命令输出都保持英文解析不会乱。你真正输出给用户的中文提示还是在 echo 字符串里不受影响。至于最精简的容器环境连tput和who都没有怎么处理我的习惯是每一条命令外面都加一个command -v判断如果不存在就跳过对应信息不让脚本因单条命令缺失而整体崩溃。容器环境一般也不是给运维长时间登录用的显示个基本的内核和 CPU 核数就够了。5. 从脚本到习惯我的几点实操体会这套登录显示系统信息的脚本我前前后后进化过四个大版本。第一版只是把几条命令简单拼在一起没有任何判断和颜色后来慢慢加入了兼容性守卫、角色识别、超时保护用了这么久最大的收获不是“能看到系统信息”而是让我养成了每次登录后先花三秒扫一遍状态的肌肉记忆。有些朋友可能会觉得系统信息每次登录都显示看久了就腻了甚至忽略掉异常。我建议你是可以给脚本加一个“仅在有异常时高亮”的逻辑。比如磁盘使用率超过 90%那行数字用红色显示负载超过 CPU 核数就用黄色标记。这样平时是清爽的蓝绿色有异常时一眼就能捕捉到。后续扩展的方向也很多。比如把脚本改成根据~/.ssh/config中的 Host 别名判断当前登录的是哪一组机器或者联合作战室里的故障通告脚本里读一个远端状态文件登录时打印出当前是否有正在进行的网络割接或变更。只要把这条思路做成框架你想加什么信息都是加一行命令的事。如果你是从零开始接触 Bash别被上面这些兼容性细节吓到。先把你日常最常用的uname -a、free -h、df -h、uptime塞进一个脚本放到~/.bashrc里体验一次再逐步完善它。脚本这个东西写着写着就会了踩过一两次坑很多原则自然就理解了。我现在看到一台新机器第一件事就是装上这个登录脚本三秒内就能知道它是什么环境、状态如何这种“站在同一视角看所有机器”的体验确实帮我省下了大量敲重复命令的时间。最后分享一个小技巧如果你不想登录时每次都输出一大串信息又不想去掉这个功能可以给脚本加一个环境变量开关比如设置了QUIET_LOGIN1就只显示错误级别的内容。这样既保留了巡检能力又可以在频繁操作时清空屏幕。我自己的服务器上是把默认级别开到完整显示遇到需要在宿主机上快速操作的时候就会执行QUIET_LOGIN1 ssh target-host两种模式之间切换非常顺滑。这个设计思路同样适用于其他任何你觉得“有点烦但必须保留”的登录提示。
返回列表