刚接手一台服务器,第一件事我会敲w;有人跟我说"系统有点卡",我第一反应也是w;排查异常登录、清理僵尸会话、写巡检脚本,翻来覆去用的还是那几个命令。但有意思的是,很多做了两三年的运维,被问到"Linux如何查看当前登录用户",能脱口而出who,再问w和who的区别、last和lastlog的差异,就开始含糊了。
这个标题看起来简单,实际是一个非常典型的"一题多解"场景:同一个需求,命令不同、输出不同、适用时机也不同。今天我就把这块掰开揉碎讲清楚,从最基础的who到历史登录审计,再到配合排查实际故障的完整思路,顺便把面试里喜欢追问的细节也一并交代了。
1. 先分清:who、w、users、whoami,到底谁在干"查看登录用户"的活
很多人记命令是死记硬背,who看登录、w看登录、last看登录,但从来不想这些命令背后的设计差异。实际上它们是四个不同的工具,定位完全不同。
1.1 who:最朴素的"现在谁在系统里"
who是最直接的命令,不加任何参数,输出长这样:
$ who root pts/0 2025-01-12 09:23 (192.168.1.10) zhangsan pts/1 2025-01-12 10:02 (192.168.1.23) lisi pts/2 2025-01-12 10:45 (192.168.1.34)四列信息分别是:用户名、终端类型、登录时间、来源地址。
这里有个细节值得注意:第三列后面的括号里如果是IP,说明是远程SSH登录;如果是:0或者tty1这类,说明是本机物理终端登录。做机房巡检或者排查内网攻击源的时候,这一列就是最重要的线索。
who命令还有很多参数,我常用的一个是who -u,会多显示一个"空闲时间"和"进程ID"。空闲时间这一列在排查"谁挂在服务器上不动"的时候很实用,后面讲实战排查我会细说。
1.2 w:登录名单之外,它连"正在执行什么命令"都给你列出来
你可能要问了,who已经能看登录用户了,为什么还要w?
这就是典型的"够用"和"好用"的区别。w的输出比who丰富得多:
$ w 10:56:32 up 2:14, 3 users, load average: 0.08, 0.03, 0.05 USER TTY FROM LOGIN@ IDLE JCPU PCPU WHAT root pts/0 192.168.1.10 09:23 1:33 0.12s 0.02s vim /etc/nginx/nginx.conf zhangsan pts/1 192.168.1.23 10:02 4:25 0.05s 0.05s -bash lisi pts/2 192.168.1.34 10:45 0.00s 0.03s 0.02s top第一行是系统整体状态:当前时间、系统运行时长、用户总数、负载均值。这一行在排查负载问题时可以直接当"开胃菜"。
然后是每个登录用户的详情:
IDLE:用户空闲了多久,长时间高数值说明这个会话基本挂死了JCPU:该终端所有进程累计消耗的CPU时间PCPU:当前前台进程消耗的CPU时间WHAT:用户当前正在执行的命令
w和who的关系,我习惯这么理解:who是"点名册",w是"点名册加实时监控"。如果你只有时间记一个命令,记w,它覆盖了who的全部功能还多得多。
1.3 users:简洁到可以写进脚本
如果说who是点名册,那users就是"签到单"——它只输出用户名,一行搞定:
$ users root zhangsan lisi它的价值主要体现在脚本里。比如我要写一个巡检脚本,判断"是否有除root之外的用户在线",直接解析users的输出比解析who简单得多:
#!/bin/bash # 检查是否有非root用户在线 online_users=$(users) if echo "$online_users" | grep -qv root; then echo "警告:存在非root用户在线:$online_users" fi注意users输出中的用户名可能有重复,比如同一个用户开了两个终端。如果需要去重,可以用users | tr ' ' '\n' | sort -u。
1.4 whoami:它其实不回答"当前有哪些用户"
whoami严格来说不在"查看登录用户"的范畴里,但我发现新手经常把它和who搞混——who是看系统里有谁,whoami是看"我是谁"。
$ whoami root这个命令的真实逻辑是读取当前进程的UID,然后映射用户名。它有个很隐蔽的坑:如果你先su - zhangsan切换了用户,再在同一个Shell里执行whoami,输出的是zhangsan而不是root。但如果你用sudo -u zhangsan执行,结果会受sudo配置影响。
所以在排障的时候,如果发现"执行命令的人"和"登录的人"对不上,多半是发生了用户切换。结合后面要讲的last命令,就能拼出完整的操作轨迹。
2. 查看登录用户的底层档案:utmp、wtmp、btmp 三个文件
光会敲命令不算真懂,你得知道这些命令的数据从哪来。Linux 把登录信息存在三个二进制文件里,绝大多数"查登录"的命令都是在读它们:
| 文件 | 路径 | 记录内容 | 谁来写 |
|---|---|---|---|
| utmp | /var/run/utmp | 当前在线会话 | who、w、users读取它 |
| wtmp | /var/log/wtmp | 历史登录/登出记录 | last读取它 |
| btmp | /var/log/btmp | 失败登录记录 | lastb读取它 |
utmp里的记录是动态的:用户登录时写入、登出时删除,所以它永远只反映"此刻谁在线"。
wtmp是追加写入,只增不减,记录每一次完整的登录和登出会话。这也是为什么重启服务器后,last依然能看到过去的记录——它读的是磁盘里的wtmp,不是内存。
btmp是个容易被忽略的存在,但它恰恰是排查暴力破解的第一手资料。每次SSH密码输错,Linux都会往btmp里写一条失败记录,用lastb可以查看:
$ sudo lastb root ssh:notty 118.31.xx.xx Tue Jan 14 03:22 - 03:22 (00:00) admin ssh:notty 118.31.xx.xx Tue Jan 14 03:22 - 03:22 (00:00)你不难发现,lastb输出的头几行经常是乱糟糟的一堆陌生IP,后面跟的全是常见用户名,这是典型的自动化爆破特征。所以我的习惯是:每天瞄一眼lastb | head -20,比看什么安全设备告警都来得直接。
还有个文件容易被遗忘:/var/run/utmp在系统重启后会被清空重建,这也是为什么who只能看到本次开机以来的在线用户——重启前的登录会话全部"断线"了,自然就不算"当前登录"。
3. 翻开历史账本:last 和 lastlog 的正确用法
在线用户只是一瞬间的状态,排查问题的时候你往往更关心"谁在什么时候登录过"、"这个账号有没有被使用过"。
3.1 last:完整的登录/登出流水账
last读的是wtmp,输出每次会话的记录:
$ last root pts/0 192.168.1.10 Mon Jan 13 09:23 still logged in zhangsan pts/1 192.168.1.23 Mon Jan 13 10:02 still logged in lisi pts/2 192.168.1.34 Mon Jan 13 10:45 still logged in root pts/0 192.168.1.10 Mon Jan 12 09:23 - 18:30 (09:07) reboot system boot 2.6.32-642.el6 Mon Jan 12 08:00 still running注意几类特殊输出:
still logged in:这次会话还没登出,就是当前在线reboot system boot:系统启动记录,wtmp里会写,所以last能看出系统何时重启过- 18:30表示登出时间,括号里是该会话的总时长
last支持按用户名过滤,比如只看 root 的登录历史:
$ last root也可以看某个终端的历史:
$ last pts/0这个命令在审计场景特别有用。举个例子,有次同事说某个配置文件被改了,但都否认是自己改的,我用last拉出最近两天所有登录过这台机器的人,再配合history时间戳一对比,几分钟就定位到了。
3.2 lastlog:每个账号的"最后一次登录"登记表
lastlog读的是/var/log/lastlog,它会列出系统里所有有登录记录的账号(其实是所有UID达到阈值的用户),输出每个账号最后一次登录的时间和来源:
$ lastlog Username Port From Latest root pts/0 192.168.1.10 Mon Jan 13 09:23:28 +0800 2025 daemon **Never logged in** bin **Never logged in** zhangsan pts/1 192.168.1.23 Mon Jan 13 10:02:11 +0800 2025注意last和lastlog的区别:
last看"每一次会话",是流水账lastlog看"每个账号的最后一次",是一张汇总表
lastlog最大的价值在于发现"异常存活的账号"。比如一个系统里有一百个账号,看着都正常,但跑一遍lastlog,发现其中二十个显示**Never logged in**,而这些账号偏偏能SSH登录,这就要警惕了——正常没人用的账号通常会被锁掉。
3.3 登录记录文件会无限增长吗?会,所以要学会管理
wtmp和btmp是追加写入的二进制文件,日子久了体积会膨胀。尤其btmp,如果服务器常年被暴力扫描,可能几个月就长到几个GB。我在生产环境见过 8GB 的btmp,lastb一执行直接卡住。
常规做法是用 logrotate 管理这两个文件。系统一般自带配置,在/etc/logrotate.d/下会有对应规则,但要确认一下实际生效情况。我自己维护的服务器会单独加上按周轮转、保留4周的策略:
/var/log/btmp { weekly rotate 4 compress missingok }还有个小技巧:日志轮转之后lastb默认只读当前的btmp,要看上一周的得用lastb -f /var/log/btmp.1.gz配合zcat来处理,这个到用的时候再查就行。
4. 实战排查:从"有用户在线"到"定位异常会话"的完整链路
命令都认识了,接下来聊聊真正的工作场景。这里我挑三个最常见的诉求展开:判断卡顿是否与登录用户有关、找出长时间不动的僵尸会话、识别可疑的登录来源。
4.1 系统变卡:先用 w 看负载和现场命令
服务器突然变卡,登录上去第一步我会敲:
$ w 11:30:15 up 30 days, 5:02, 5 users, load average: 15.08, 12.03, 9.05 USER TTY FROM LOGIN@ IDLE JCPU PCPU WHAT root pts/0 192.168.1.10 09:23 0.00s 0.12s 0.02s w zhangsan pts/1 192.168.1.23 10:02 1:02 12.5s 11.3s python3 /tmp/check.py lisi pts/2 192.168.1.34 10:45 0.05s 0.03s 0.02s top重点看两个地方:load average和WHAT列。如果负载很高,而某个用户的WHAT列显示一个你不知道的进程持续在跑,十有八九问题就在这个会话上。
上面这个例子,zhangsan在跑/tmp/check.py,CPU时间累计了 12.5 秒,继续跟踪就应该顺着流程去看这个进程到底在干什么:
$ ps -ef | grep check.py $ ls -l /proc/<pid>/cwd/proc/<pid>/cwd可以显示进程的工作目录,写脚本的服务器上这个技巧几乎每天都要用。
4.2 空会话和僵尸终端:IDLE 高到离谱的会话怎么清理
w输出里的IDLE列,超过几个小时甚至几天的会话,基本可以断定是无用的遗留会话。比如某天你看到:
$ w USER TTY FROM LOGIN@ IDLE JCPU PCPU WHAT olduser pts/5 10.0.0.55 2025-01-05 14:20 22:15m 0.02s 0.01s -bash22:15m表示空闲了22小时15分钟,这种会话占着终端资源,虽然不会直接拖垮系统,但如果批量存在,也会占用大量pts设备号,导致新连接分配不到终端。
处理僵尸会话的步骤我一般这么走:
- 先确认该用户的会话数:
who -u,看第二列终端号 - 确认没有关键任务在跑:
ps -ef | grep pts/5,排除掉还在跑的进程 - 杀掉会话对应的进程,或者直接踢掉:
pkill -9 -t pts/5
这个操作不是随手就能做的。生产服务器上,一个挂着vim没保存的会话被强杀,数据就丢了。所以我给自己定了个规矩:踢会话之前,至少ps -t pts/5看一眼有没有正在编辑的进程。
4.3 可疑来源IP:一眼识别非内网登录
运维的服务器一般只开放了内网SSH,如果w或者who里出现陌生的公网IP,这就要立刻警觉了。
比如:
$ who root pts/0 192.168.1.10 2025-01-13 09:23 admin pts/3 185.220.101.4 2025-01-13 11:05内网网段是192.168.1.x,突然冒出一个185.x.x.x的登录,来源地明显不对,就要走异常排查流程:
- 先看历史记录,确认这个IP是不是第一次出现:
last | grep 185.220.101.4 - 看失败记录里有没有大量尝试:
lastb | grep 185.220.101.4 | wc -l - 看这个会话的
WHAT列在做什么命令,立刻决定是否踢出:pkill -9 -t pts/3
再补充一个细节:如果你发现last里某个用户的登录记录全来自陌生IP,而他自己说没登录过,那就要考虑账号密码泄露了,第一时间passwd 用户名重置密码,同时把所有在线会话踢掉,然后去~/.ssh/authorized_keys里检查有没有多出来的公钥。别问我是怎么知道要查这玩意的,都是教训换来的。
4.4 盯着登录动态:watch + 命令组合拳
排查不是一次性动作,很多时候你需要持续观察。我最常用的组合是:
$ watch -n 1 'who; echo "---"; w'watch -n 1表示每秒钟刷新一次,随时能看到有没有新会话进来、已有会话在跑什么命令。这在多人共用一台测试机、排查"谁又在乱执行命令"的场景里特别好使。
想要记成日志的话,可以加个时间戳追加到文件:
$ while true; do date >> /tmp/login_monitor.log; w >> /tmp/login_monitor.log; sleep 60; done这种粗糙的方案在应急场景比装监控系统快得多,胜在五分钟内就能跑起来。
5. 面试被追问的隐藏维度:终端类型、切换用户与 "仍然登录中"
这个标题在面试题里出现的频率挺高的,但面试官一般不会只满足于"背命令"。基于我前面讲的内容,有几个经常被追问的细节值得单独拎出来说。
5.1 TTY 和 PTS 到底代表什么
who输出的第二列,pts/0、pts/1、tty1这些,面试时经常被拿来作文章。
tty1~tty6:本机物理终端(按下 Ctrl+Alt+F1~F6 切换的那种)pts/N:远程终端或伪终端,SSH登录、xshell、securecrt 连接都属于这类
更底层的说法是,pts是 "pseudo-terminal slave" 的缩写,它由sshd等程序动态分配,所以你SSH登录一次,pts的编号通常会加1。同一个用户多次SSH,会占用多个不同的pts/N,在who里就会有这个用户的多个登录记录。
5.2 reboot 记录为什么也会出现在 last 里
这是一个很能拉开差距的细节。last会输出系统启动记录,是因为wtmp在系统启动时会被写入一条reboot记录。这条记录的 TTY 列显示system boot,FROM 列显示内核版本。
所以当你用last排查问题时可以顺便确认系统何时重启过,比如:
$ last reboot reboot system boot 5.4.0-26-generic Mon Jan 13 08:00 still running要是想单独看所有重启记录,last reboot是最快的命令,没有之一。
5.3 su 和 sudo 切换后的"表面身份"与"真实身份"
表面上who看到的是登录用户名,但如果你在会话里执行了su或者sudo,后续命令的真实执行身份已经变了。这时候有几个命令可以确认"我到底是以什么身份在干活":
$ id -u # 显示当前有效用户的UID $ whoami # 显示当前有效用户名 $ logname # 显示最初登录的用户名(如果依次su了多次,这个依然是最早那个)logname是个冷门命令,但它有个独特价值:whoami会随su切换而变化,logname不会。所以在面试里可以这样答:"whoami回答的是当前有效身份,logname回答的是初始登录身份,两个命令在排查提权和身份切换场景时需要配合使用。"
5.4 last 状态全解析:from 里的 IP 和 "still logged in" 的判断逻辑
last的输出有个很容易读错的点:如果会话正常退出,你会看到- 登出时间;如果会话还在线,你会看到still logged in。这两个状态的判断依据就是utmp里的对应记录是否已被清除。所以last和who拿到的是同一套数据,只是last还能对比出"历史完整链路",而who只能看"此刻的快照"。
有次一个同事问我:"为什么last里显示某用户 still logged in,但who里看不到这个人?"这种情况多半是会话异常中断,utmp记录残留了。处理方法是把对应终端的进程清一遍,或者直接重启一下 sshd。反正记住一点:who看不见了,但last还说留着,说明会话异常断开,属于需要清理的"烂尾会话"。
6. 实际维护中我沉淀下来的一套"查登录"习惯
前面讲的知识点比较多,最后分享一点我实际干活时的操作习惯。单独看每个命令都很简单,但组合起来能覆盖绝大多数日常需求。
我自己的例行巡检是这么做的:SSH 上去先执行w,看当前用户、负载、正在执行的命令,这一步能筛出90%的表面问题;然后last | head -20,确认最近有没有异常的登录来源;最后lastb | head -20,看有没有明显的爆破试探。三条命令不到十秒,一台机器的"登录健康度"基本心里有数。
如果是排查具体某个用户的操作轨迹,我会按时间线串联:
last 用户名:定位这个用户最近几次登录的终端、IP、时间who -u 用户名:确认该用户当前是否在线、在哪个终端ps -t pts/N:看这个终端的进程列表,尤其是还活着的命令- 需要时翻
.bash_history,对照时间戳还原操作
再分享一个很多人忽略的点:如果你管理的服务器较多,建议把常用的"查登录"命令统一封装成一个脚本,输出格式固定,批量执行时能省大量时间。我自己写过一个简单的巡检脚本,核心逻辑就是执行w、last、lastb三个命令,把结果归档到固定目录,每天定时跑一次。不求花哨,胜在稳定省心。
最后想提醒一句,w命令输出的load average是整机的平均负载,它并不是"当前登录用户造成的",两者有可能是巧合。所以看到高负载先别急着怪在线用户,还要结合top、iostat这些工具往下查。这也是面试官最爱挖的坑之一:一上来就说"负载高肯定是因为有人在乱跑命令",这种答案一看就是背出来的,缺少实际排查经验支撑。
把这些命令吃透,不光是为了应付面试,更是为了在服务器真正出问题的时候,你手里有足够多的工具去定位和还原现场。我见过不少运维同行,遇到"有异常登录"第一反应是把密码改了、把用户踢了,但从来没想过先去看last完整还原一遍操作轨迹——这才是这个题目背后真正值钱的东西。