我刚接触Linux那会儿,最喜欢翻的两个目录是/etc和/home。一个装着系统的"命根子"配置,一个装着所有用户的"小窝"。后来带过几个新人,发现大家最容易懵的也是这两个地方——尤其是/etc/passwd这个文件,乍一看就是一堆冒号分隔的乱码,但只要你搞懂了它,Linux的用户管理体系基本就通了半边天。这篇文章我就把第8集的内容串起来聊聊,从/etc目录讲到Shell切换,再到用户组管理和正则匹配,全是实操里反复用到的东西。
1. 从/etc目录谈起:先搞清"系统的灵魂目录"到底装了什么
1.1 为什么/etc值得你花时间仔细看一遍
/etc(ETCetera的缩写,意思是"等等其他")可以说是Linux系统里最重要的配置目录。你平时改IP地址、配DNS、设置开机自启、管理用户密码,最终落地的文件几乎都在这里。很多新手出问题,根子就在于不知道配置文件在哪、格式是什么、改完要不要重启服务。
我习惯把/etc下的文件分成三类:
- 系统级配置:比如
/etc/fstab(开机挂载分区)、/etc/hosts(本地域名解析)、/etc/resolv.conf(DNS配置)、/etc/sysctl.conf(内核参数) - 服务与应用配置:比如
/etc/ssh/sshd_config、/etc/nginx/nginx.conf、/etc/crontab(定时任务) - 用户与认证信息:今天的主角
/etc/passwd、/etc/shadow、/etc/group
其中用户管理这块,核心是三个文件:/etc/passwd存用户基本信息,/etc/shadow存加密后的密码和密码策略,/etc/group存用户组信息。这三个文件就是Linux用户体系的"台账",你用什么命令管理用户,本质上都是在改这几个文件的内容。
1.2 /etc/passwd的字段拆解:冒号分隔的"用户档案表"
/etc/passwd每一行代表一个用户,用冒号分成7个字段。比如我随便写一行:
dev_user:x:1001:1001:Dev User:/home/dev_user:/bin/bash字段含义分别是:
| 字段位置 | 含义 | 示例值 | 说明 |
|---|---|---|---|
| 1 | 用户名 | dev_user | 登录时输入的名字 |
| 2 | 密码占位符 | x | 真正密码在/etc/shadow里,这是安全机制 |
| 3 | UID | 1001 | 用户ID,0是root,1-999通常是系统用户 |
| 4 | GID | 1001 | 用户的主组ID |
| 5 | 注释信息 | Dev User | 一般是姓名或用途说明 |
| 6 | 家目录 | /home/dev_user | 登录后的起始目录 |
| 7 | 登录Shell | /bin/bash | 登录后默认使用的Shell |
刚学的时候有个问题很容易忽略:为什么密码字段是个x而不是一串加密字符串?早期Unix确实把加密密码直接放在这个文件里,但后来发现/etc/passwd是所有人都能读的,把密码哈希放这里等于公开了攻击样本。于是就把哈希挪到了只有root能读的/etc/shadow里,原位置留个x做标记。这个设计思路你品一品——权限最小化,能不给权限就不给权限。
2. Shell切换的几种姿势:chsh、$SHELL变量与登录Shell的关系
2.1 什么是登录Shell,它和终端里敲命令有什么区别
Shell就是你的命令解释器,你在终端里敲的每一行命令都是交给它执行的。Linux默认的Shell通常是bash,但也有很多人用zsh、fish。这里有个概念要分清:登录Shell和非登录Shell。
- 登录Shell:你通过输入用户名密码登录系统时启动的Shell,它会加载
/etc/profile和~/.bash_profile这类全局级配置 - 非登录Shell:你在已登录环境里再开一个子Shell(比如在bash里直接敲
bash),它主要加载~/.bashrc
这就导致一个经典问题:你改了~/.bashrc里的别名,重新登录后却"没生效"。其实不是没生效,而是登录Shell可能压根没读这个文件,或者其它配置文件把你的设置覆盖了。要确认当前Shell类型,可以看$SHELL变量和特殊变量$0:
echo $SHELL # 显示默认Shell路径,比如/bin/bash echo $0 # 显示当前正在运行的Shell,比如-bash/-zsh2.2 chsh命令:永久切换默认Shell的正规方式
临时切换Shell很简单,直接敲对应的Shell名就行。但永久切换就要用chsh(change shell)了。新手最容易犯的错是直接改/etc/passwd里的最后一个字段,手一抖写错路径,下回登录直接进不去系统。用chsh能规避这个问题,因为它会帮你检查Shell路径是否合法(是否存在于/etc/shells里)。
chsh -s /bin/zsh执行完会提示你输入密码,然后修改/etc/passwd里对应用户的Shell字段。改完重开终端生效,用echo $SHELL验证。注意,chsh只影响你当前登录用户,想改其他用户就得sudo chsh -s /bin/zsh 用户名。
这里有个实际工作中的场景:我有一台服务器,默认用/bin/sh的系统用户特别多,但某天需要给某个服务账号切到bash来执行脚本,当时就是用chsh -s /bin/bash 服务账号名完成的。改之前务必确认目标Shell已经装好了,不然容易出现"服务启动不了,日志里报错找不到解释器"的情况。
2.3 /etc/shells的限制:为什么有些Shell路径会被拒绝
刚才提到/etc/shells,这个文件里列出了系统认可的所有Shell路径。chsh会检查你指定的Shell是否在这个列表里,不在就报错。设计这个机制的目的是防止你把登录Shell改成一些奇怪的程序,比如改成/bin/ls,那用户一登录看到的不是提示符而是一堆文件列表。所以当你编译安装了新Shell(比如从源码安装zsh到/usr/local/bin/zsh),记得先把它加进/etc/shells再chsh。
提示:修改
/etc/shells要root权限,加一行/usr/local/bin/zsh,之后再执行chsh -s /usr/local/bin/zsh就顺了。
3. 用户与组管理:从useradd到usermod的完整实操链路
3.1 useradd的默认行为:为什么新建用户后没有家目录
useradd是创建用户的命令,但很多人上来就踩坑——新建完用户,用su切过去发现提示"no directory",这才意识到没建家目录。原因在于不同发行版的useradd默认行为不一样。有些系统默认不创建家目录,你需要显式加-m参数:
sudo useradd -m -s /bin/bash dev_user这条命令同时做了三件事:创建名为dev_user的用户,给它建家目录/home/dev_user,指定登录Shell为/bin/bash。如果你还想指定UID/GID,可以用-u和-g:
sudo useradd -m -u 1500 -g dev_group -s /bin/bash dev_user这里的-g指定的是主组(primary group),后面还能用-G指定附加组(supplementary group)。主组决定用户创建文件时默认的组归属,附加组决定用户额外拥有哪些组的权限。两者别搞混,实际运维里因为主组和附加组搞混导致权限无法预期的例子非常多。
3.2 /etc/default/useradd和/etc/login.defs:改默认配置比每次敲参数更省事
如果你嫌每次创建用户都要敲一堆参数,可以去改两个配置文件:
/etc/default/useradd:定义默认家目录路径、默认Shell、默认的SKEL目录(新家目录的模板来源)/etc/login.defs:定义UID/GID范围、密码过期策略、创建家目录的默认行为
比如login.defs里有一项CREATE_HOME,有些版本设成no,这就是新建用户没有家目录的直接原因。改成yes就能让所有新用户默认有家目录,不用每条命令都加-m。
3.3 usermod:用户信息调整的"瑞士军刀"
用户建好之后,调整信息就靠usermod。它的用法和useradd几乎一致,只是把"创建"变成"修改":
sudo usermod -l new_name dev_user # 改用户名 sudo usermod -u 2000 dev_user # 改UID sudo usermod -g dev_group dev_user # 改主组 sudo usermod -aG docker dev_user # 把用户加入docker组(-a必须加,否则会被移除原有附加组)-aG里的-a(append)特别关键。很多人在改附加组时忘记加-a,结果用户从其他组里被"踢"出来了。我有一次就是把一个用户从sudo组里踢了,对方半天登录不了管理后台,排查了一会儿才发现是-G覆盖了原有组。记住:修改附加组永远带上-a。
3.4 userdel:删除用户没那么简单
删除用户用userdel,但单纯执行userdel 用户名可能留下家目录和邮件目录。所以推荐用-r参数:
sudo userdel -r dev_user这个-r会同时删除用户的家目录和邮件池,干净利落。不过有个细节要注意:如果被删除的UID还被文件占用(比如某个文件属主是这个UID,但用户名已经删了),那些文件就会显示成无主文件的数字ID。所以删除前最好先find / -user dev_user扫一下,确认没有重要文件再动手。
3.5 groupadd和groupdel:组管理的基本操作
组就是用户的集合,用groupadd创建、groupdel删除:
sudo groupadd dev_group sudo groupadd -g 2000 dev_group # 指定GID sudo groupdel dev_group删除组之前要确认组里没有用户的主组指向它,否则报错。实际操作中遇到这种报错,得先用usermod -g把相关用户的主组改掉,再来删组。查看用户属于哪些组,用id命令或groups命令:
id dev_user groups dev_usergroups输出的是用户所属的所有组,但注意它直接读/etc/group,所以即时生效。而id显示的信息更全,包括UID/GID和所有附加组,排查问题我推荐id。
4. passwd命令的隐藏技能与密码安全实操
4.1 修改密码的基本操作和权限边界
平时我们用passwd改自己的密码,root能改任何人的密码。这里说几个不那么"新手友好"的用法:
passwd # 改当前用户密码 sudo passwd dev_user # root强制改指定用户的密码 sudo passwd -l dev_user # 锁定账号(Lock) sudo passwd -u dev_user # 解锁账号(Unlock) sudo passwd -e dev_user # 强制用户下次登录时改密码(Expire) sudo passwd -n 7 -x 90 dev_user # 设置密码最短使用7天,最长90天-e这个参数在实际运维里非常有用。比如你给某个新员工开通账号,初始密码设置完之后执行passwd -e,对方第一次登录就被逼着改密码,避免长期使用连你自己都知道的初始密码。
4.2 /etc/shadow里那些冒号字段到底什么意思
/etc/shadow也被冒号分隔,但字段更多,核心是这几项:
dev_user:$6$xxxx...:19000:7:90:7:30::| 字段 | 含义 | 说明 |
|---|---|---|
| 1 | 用户名 | 对应/etc/passwd |
| 2 | 密码哈希 | $6$开头是SHA-512算法 |
| 3 | 最后一次修改密码的时间 | 从1970-01-01起的天数 |
| 4 | 密码最短使用天数 | 0表示随时可改 |
| 5 | 密码最长使用天数 | 99999表示永不过期 |
| 6 | 过期前警告天数 | 7表示提前7天提醒 |
| 7 | 宽限天数 | 密码过期后还能登录的天数 |
| 8 | 账号失效时间 | 空表示永不失效 |
| 9 | 保留字段 | 一般空着 |
看懂这个文件,你就能解释很多"灵异现象"。比如某天用户突然登录不了,提示密码过期,那多半是第5字段设了有效期,时间到了;又比如账号明明被锁了,手动改/etc/shadow把密码字段前面加个!即可锁号,passwd -l做得也是类似的事。
4.3 批量处理密码:chpasswd和expect场景
如果有几十个测试用户要统一设密码,别一个个passwd,用chpasswd:
echo "dev_user:NewPass123" | sudo chpasswd还可以配合文件批量处理:sudo chpasswd < users.txt,文件格式同样是用户名:密码,一行一个。这个命令在自动化脚本里尤其顺手,但注意密码会出现在命令行历史里,自己机器玩玩没问题,生产环境建议配合密钥登录,少用明文密码传递。
5. 正则表达式:用户管理场景下的实用匹配技巧
5.1 为什么学用户管理一定要碰正则
前面提到的/etc/passwd、/etc/group都是文本文件,既然管理用户最终都是操作文本,那就免不了要用正则去搜索、过滤、统计。举个最常见的场景:我想找出所有UID大于1000的普通用户,用grep加正则一条命令搞定:
grep -E '^[^:]+:[^:]+:(1[0-9]{3,}|[2-9][0-9]{3,}):' /etc/passwd这个正则的意思拆开看:^[^:]+匹配行首连续非冒号字符(用户名),[^:]+匹配密码字段,(1[0-9]{3,}|[2-9][0-9]{3,})匹配1000以上的数字(要么1000-1999,要么2000以上)。如果不用正则,你得先cat /etc/passwd再肉眼找,用户一多就废了。
5.2 grep、egrep和正则符号速记
先分清grep和egrep(等价于grep -E):egrep支持扩展正则,可以直接用+、?、|、{}这些高级符号,普通grep需要用反斜杠转义才能用,麻烦还容易错。建议日常统一用grep -E。
几个高频符号:
| 符号 | 含义 | 示例 |
|---|---|---|
^ | 行首 | ^dev匹配以dev开头的行 |
$ | 行尾 | bash$匹配以bash结尾的行 |
. | 任意单个字符 | a.c匹配abc/adc |
* | 前一个字符重复0次或多次 | ab*c匹配ac/abbc |
+ | 前一个字符重复1次或多次 | ab+c匹配abc/abbc但不匹配ac |
[] | 字符集 | [Dd]ev匹配Dev或dev |
[^] | 排除字符集 | [^:]匹配任意非冒号字符 |
| | 或 | dev|test匹配dev或test |
{n,m} | 重复次数范围 | [0-9]{3,}匹配3位以上数字 |
() | 分组 | (ab)+匹配ab/abab |
5.3 实际案例:用正则筛选需要清理的僵尸账号
我前阵子接手一台服务器,需要盘点哪些账号是"垃圾账号"。当时用了几条正则命令做筛选:
先找出所有登录Shell是/usr/sbin/nologin的账号,这些一般是服务账号,不能登录shell,按理说不用清理:
grep -E '/usr/sbin/nologin$' /etc/passwd再找出所有家目录在/home下但已经半年没登录的账号。登录时间得去lastlog看,所以用lastlog配合awk处理:
lastlog -t 180 | awk '{print $1}' | grep -v -E '^(Username|root)$'这里的-v是反向匹配,排除表头和root。正则里^Username$精确匹配整行。最后用usermod把确认不用的账号锁掉:
sudo usermod -L 用户名整个过程下来,正则不是炫技,而是把文本处理的效率拉满。你想精准操作文本,正则绕不过去。
5.4 正则的常见陷阱:贪婪匹配和转义问题
新手写正则最容易踩两个坑。
第一个是贪婪匹配。默认情况下*和+是贪婪的,会匹配尽可能多的字符。比如你用^a.*:匹配abc:def:,它会匹配到最后一个冒号,而不是第一个。想改成非贪婪,在*或+后面加?(但要注意,这个语法在grep -E里不一定支持,得看版本)。实战中我通常宁可把正则写得"窄"一点,也懒得和贪婪较劲。
第二个是转义。在/etc/passwd里冒号是分隔符,你想匹配冒号本身没问题,但如果你想匹配[或]这种正则符号,就得加反斜杠转义。比如grep -E '\[root\]' /etc/passwd匹配字面上的[root]字符串。很多人在这里反复试错,其实记住一点就行:你想匹配的字符碰巧是正则元字符,那就加\让它变回普通字符。
6. 把这套知识串起来:一个从零创建用户到配置环境的完整示例
光讲命令太散了,我带你把今天的内容串成一个真实场景。假设我现在要给一个来自某高校实验室的协作者开一个账号,名字叫lab_user,让他能用bash、能自己装软件(把他加进sudo组),同时限制密码90天过期。完整操作如下:
# 1. 创建用户并指定家目录和Shell sudo useradd -m -s /bin/bash -c "Lab Collaborator" lab_user # 2. 设置初始密码,并强制首次登录修改 echo "lab_user:TempPass2024" | sudo chpasswd sudo passwd -e lab_user # 3. 将用户加入sudo附加组 sudo usermod -aG sudo lab_user # 4. 验证用户信息 id lab_user grep -E '^lab_user:' /etc/passwd这条链路跑完之后,grep查到的结果就是一条标准的/etc/passwd记录。如果后续要删除这个账号,一个sudo userdel -r lab_user就能收尾。这里再提醒一下,userdel -r会删家目录,当心客户数据。
再看一个自动化场景。某次我需要给模拟项目X的20个临时用户批量重置密码,密码统一为随机生成的串,然后强制所有人下次改密码:
for u in $(cat user_list.txt); do echo "$u:$(openssl rand -base64 12)" | sudo chpasswd sudo passwd -e "$u" done这里用了openssl rand生成随机密码,chpasswd批量写入,最后强制过期。你看看,这几条命令背后全都是今天讲的文件、命令和正则思路。
7. 踩坑记录:用户管理里的三个典型翻车现场
写到最后,我把自己见过(也踩过)的几个坑列出来。这些内容正经文档里很少细讲,但实际运维碰到的概率很高。
第一个坑:手动改/etc/passwd导致系统登录崩溃。有一次图省事,我直接用vim给某个用户的家目录路径加了个字,结果多打了一个字母。用户那边完全登录不进去。后来用系统修复模式进去改回来的。所以改用户信息老老实实用usermod,别手搓/etc/passwd。如果非要手改,先把/etc/passwd备份一份:cp /etc/passwd /etc/passwd.bak。
第二个坑:usermod -G把用户从sudo组踢出。这个前面已经提过,但我愿意再说一次,因为太典型了。当时我想把一个用户加入docker组,用的是sudo usermod -G docker 用户名,忘了加-a,结果sudo组没了。幸亏机器上还有其他管理员账号,local console还能救。教训就是:写进肌肉记忆,附加组操作永远-aG。
第三个坑:正则写错导致批量锁定账号。之前写清理脚本,打算锁掉所有登录Shell为/bin/false的用户,正则写成了grep -E '/bin/false' /etc/passwd,结果把注释信息里带有"false"字样的用户也匹配进去了。幸好锁的是几位同事的测试号,被念叨了好几天。从那以后我写这种批量操作,都会先跑一遍grep单独查看匹配结果,确认无误后再接usermod。宁可多敲一条命令,也别批量误伤。
8. 收尾:一点实际使用后的体会
这套内容学完之后,我建议你把重点放在"文件-命令-正则"三者之间的关系上。命令记住了只能解决眼前事,理解了/etc/passwd、/etc/shadow、/etc/group这几个文件的结构和字段含义,遇到任何用户异常你都能顺着文本文件找到根因。正则更是给效率加buff的工具,别怕写错,多多debug,自然就熟练了。
后面我还会继续更新Linux基础系列的后续内容,比如文件权限的深入剖析、环境变量加载顺序、systemd管理服务之类的。如果这篇文章对你有点帮助,欢迎在评论区聊聊你踩过的用户管理相关的坑,我每条都会看。