在 Linux 权限体系里,用户、用户组与附加组是最常被提起,也最容易被用错的一组概念。我见过不少类似的翻车现场:为了给某个用户加入新项目组,一条usermod -G把原有的附加组整个覆盖掉了,用户那边文件权限瞬间全乱;或者想删一个组,结果系统提示这是某用户的主组,怎么都删不掉。这些问题的根子,都在于没搞清用户、用户组、附加组底层的数据结构,以及主组、附加组、有效组之间的区别。这篇文章就从四张底层数据表讲起,再用十个能直接照抄的实战例子,把useradd、usermod、gpasswd、umask这些命令真正的行为逻辑串起来。适合刚接触 Linux 权限管理的新手,也适合要写账号批量维护脚本,或者排查权限问题的运维同学对照自查。
1. 用户与组的四张底层表:想理解命令在改什么,先看数据存在哪
很多人学 Linux 用户管理,一上来就背命令,useradd怎么加用户、groupadd怎么加组,背得滚瓜烂熟。但一旦遇到"为什么这个用户在组里却写不进目录""为什么组删不掉"这类问题就卡壳,原因就是不知道命令背后到底改了哪些文件。Linux 的用户和组信息,本质上就是四个文本文件在支撑:/etc/passwd、/etc/shadow、/etc/group、/etc/gshadow。所有用户和组相关命令,都是在读写这几张表。搞清它们,命令行为就全通了。
1.1 /etc/passwd 与 /etc/shadow:用户身份的正面与背面
/etc/passwd是用户信息的"正面",每一行代表一个用户,用冒号分成七个字段。取个典型的例子:
user01:x:1001:1001::/home/user01:/bin/bash七个字段从左到右依次是:登录名、密码占位符(真正的密码不在这里)、UID、主组 GID、用户备注信息、家目录、登录 shell。其中那个x就是密码占位,说明密码存在影子文件里。主组 GID 这个字段非常关键,它决定了用户创建文件时默认所属的组,后面实战里切换主组,本质上改的就是这一列。
/etc/shadow就是用户身份的"背面",只有 root 能读,权限通常是000或600。里面同样按行存储,密码字段是加密后的哈希,开头是!或!!表示账户被锁定。除了密码,还记录了密码的最后修改日期、最短使用天数、最长使用天数、过期警告天数、宽限天数、账户过期日期等。这些字段平时看似没人管,但企业环境里要求定期改密、账户到期回收,全都靠它。
1.2 /etc/group 与 /etc/gshadow:组信息的存储方式
组信息也存在两个文件里。/etc/group每行代表一个组,格式是四段:
devteam:x:1002:user01,user02分别是组名、组密码占位、GID、附加成员列表。注意第四段里只列附加成员,不列主组成员。很多人第一次看这个文件会疑惑:为什么明明很多用户的主组是devteam,第四段却是空的?因为主组成员关系已经通过/etc/passwd里的 GID 字段记录过了,group文件不需要重复存储。这个设计对整个用户组体系影响很大,理解它,才能理解为什么改了passwd里的 GID,主组就变了,而附加组却要改group文件。
/etc/gshadow对应存放组密码和组管理员,平时用得少,但如果要用gpasswd让普通用户管理组内成员,组管理员信息就存在这里。
1.3 主组、附加组与有效组:一个用户到底有几种"组身份"
一个用户在同一时刻可以有三个层面的组身份。主组(primary group)在创建时由/etc/passwd的 GID 指定,用户默认创建的文件和目录都属于它。附加组(supplementary groups)记录在/etc/group的第四段,用来给用户追加权限,让用户可以访问其他组的资源。有效组(effective group)则是当前进程实际生效的组身份,默认等于主组,但可以用newgrp临时切换。
用生活化的类比:主组就像身份证上的籍贯,是一个人默认的归属;附加组像各种社团会员证,代表你额外能进哪些门;有效组则是你进门时实际刷的那张卡。大多数情况下三张卡指向同一个地方,一旦需要共享目录协作、跨部门授权,这三者的区别就立刻显现出来了。后面十个别实战例子,每一个都会落实到这四个文件的具体字段变化上,这样你在执行命令时就能预估它会产生什么影响,不至于改完命令还要靠重启来验证。
2. 实战例子1-4:建号、密码、主组切换与附加组的覆盖陷阱
这一节先从最基础的四个操作入手,看useradd、passwd、chage、usermod这些命令真正做了什么。每一步我都会贴出关键输出,并解释它影响的是哪张表。
2.1 例子1:useradd 创建用户时,默认动作比你想的多
执行一条最简单的创建命令:
useradd user01看起来只是建了个用户,实际上系统默认做了四件事:在/etc/passwd追加一行、在/etc/shadow追加一行、在/etc/group新增一个同名组、创建家目录/home/user01并从/etc/skel复制初始化文件。验证一下:
grep user01 /etc/passwd /etc/shadow /etc/group id user01输出大致是:
/etc/passwd:user01:x:1001:1001::/home/user01:/bin/bash /etc/shadow:user01:!!:19723:0:99999:7::: /etc/group:user01:x:1001: uid=1001(user01) gid=1001(user01) groups=1001(user01)注意几个默认值:UID 通常从 1000 往后自动分配;主组 GID 默认为新创建的同名组 ID;家目录自动生成;shell 默认/bin/bash。如果想改变这些默认行为,就用参数显式指定,比如useradd -s /sbin/nologin -d /data/home/user01 -g devteam -G extragroup user01,分别对应 shell、家目录、主组、附加组。实际运维中,给某类服务建账号时最好显式指定-s /sbin/nologin,防止账号被用来登录交互 shell,这是很常见的安全加固项。
2.2 例子2:passwd 与 chage 组合的密码生命周期管理
新用户创建后默认是不能登录的,从上面 shadow 输出可以看到密码位是!!,表示未设置。接下来要设密码:
passwd user01 chage -l user01passwd交互式设置密码,也可以用管道配合--stdin在脚本里批量处理(CentOS 系支持)。chage -l会列出密码的过期信息,包括最近一次修改日期、密码有效期限、失效前警告天数、账户到期时间。如果要求用户首次登录必须改密码,一条命令就能做到:
chage -d 0 user01-d 0表示将密码最后修改日期设为 1970-01-01,这样系统认为密码已过期,下次登录必然提示修改密码。这个技巧在批量创建账号时非常实用,后面例子 6 还会结合脚本演示。
锁定与解锁也是高频操作:passwd -l user01锁定(shadow 密码字段前会多出!),passwd -u user01解锁。遇到员工离职、账号暂停等场景,建议用锁定而不是直接删号。账号锁定后文件还在,权限关系不乱,恢复也方便。
2.3 例子3:usermod -g 切换主组,改的其实是 passwd 文件
有时需要把用户的主组从默认的同名组切到某个业务组,比如:
usermod -g devteam user01 id user01输出里gid=1002(devteam),主组变了。但此时去看/etc/group里devteam这一行,第四段并没有新增user01。原因前面提过:主组关系记录在/etc/passwd的 GID 字段,usermod -g改的就是这一列,group文件自然无需变化。这也引出一个常见误区:有人以为把用户"加进组"就是改 group 文件,其实要加入的是主组还是附加组,操作的文件完全不同。
主组切换后,该用户之后新建的文件会归入新主组,但已存在的文件不会自动改名。如果想把已有文件也批量改归属,需要配合chown -R user01:devteam /path手动处理。另外要注意,usermod -g的参数可以是组名,也可以是 GID,保险起见用组名,避免手滑写错 ID。
2.4 例子4:usermod -G 多次执行的覆盖问题,你有没有踩过
这是所有用户与组管理里最经典的坑。假设user01本来在devteam组里,现在要让它也能访问opsgroup,于是一条命令:
usermod -G opsgroup user01 id user01输出中附加组列表只剩1003(opsgroup),之前的devteam不见了。再执行一条:
usermod -G devteam,opsgroup user01这次才同时保留两个附加组。原因在于-G的语义是"设置附加组列表"而不是"追加",每次执行都会用新列表覆盖旧列表。正确的追加姿势是加-a参数:
usermod -aG opsgroup user01-a表示 append,与-G联用才能实现"保留原有并追加"。但必须强调,-a单独用没有任何效果,必须和-G一起。另一种更不容易出错的方案是直接用gpasswd -a user01 opsgroup追加,gpasswd -d user01 opsgroup移除,语义清晰,无需关心原有列表。如果你的脚本里要批量给不同用户追加不同组,建议优先用gpasswd,避免多条usermod -G互相覆盖。
3. 实战例子5-8:删除清理、批量建号、共享目录与组成员管理
基础四个例子讲完,这一节进入更贴近真实工作场景的操作:删除用户时怎么处理遗留物、批量创建账号、以及多人协作时如何设计共享目录和管控组成员。
3.1 例子5:删用户别只顾删人,遗留文件和主组问题要一起解决
删除用户时,很多人只知道一条userdel user01就完事了。但默认情况下,userdel并不会删除用户的家目录、邮件池和/var/spool/mail下对应用户文件。如果想连同家目录和邮件目录一起删除,需要用:
userdel -r user01即便如此,用户还可能留下一些散落的文件:比如以前在/tmp、/data等目录下创建的文件,或者定时任务。删除账号后这些文件的所有者会变成一个数字 UID,时间久了会出现一堆"匿名"文件,谁也说不清是谁的。清理思路是用find / -nouser查找所有 UID 无主的文件,再人工确认处理。实测时建议限定查找范围,比如/home、/data、/tmp,全盘find /在文件多的时候会很慢。
另外,如果被删用户的主组是某个业务组,直接groupdel会失败,提示组是某用户的主组,无法删除。所以删除顺序应该先删用户或先把这个用户的主组切走,再删组:
usermod -g tempgroup user01 # 先把主组移走 groupdel devteam3.2 例子6:循环脚本批量建号,强制首次登录改密
批量创建账号是运维的常见需求,比如一次给项目组开通 10 个账号。基于例子 2 的知识,可以写一个简单的循环:
for u in user{01..10}; do useradd "$u" echo "$u:InitialPass123" | chpasswd chage -d 0 "$u" done三条核心动作分别是:建号、设初始密码、强制首次登录改密。这里我没有用passwd --stdin,是因为它在 Debian/Ubuntu 系列默认不可用,而chpasswd在主流发行版通用,兼容性更好。若要追加附加组,把useradd "$u"换成useradd -G devteam "$u"即可。
批量清理也讲究顺序,反过来执行:
for u in user{01..10}; do userdel -r "$u" done如果这批用户建号时同时创建了同名组,组未必会随userdel删除,需要再循环groupdel "$u"。批量操作脚本跑完后,务必抽几个用户做id、chage -l验证,别等用户反馈登录不上才发现问题。
3.3 例子7:用附加组 + setgid 搭一个真正的共享协作目录
多用户协作某个目录,最常见的设计就是把相关用户都加入同一个附加组,再给目录设置组权限。假设有两位开发人员user01、user02,要共享/data/project:
groupadd devteam usermod -aG devteam user01 usermod -aG devteam user02 mkdir -p /data/project chown -R root:devteam /data/project chmod 2770 /data/project关键在最后的2770。2是 setgid 位,表示目录下新建的文件会自动继承目录的用户组,避免后创建的文件变成"用户自己的私有组"导致其他成员读不了;770是目录权限,组成员可读写执行。这个模式比简单chmod 777安全得多,也比反复改主组灵活。共享目录建好后,组成员如果发现新建文件的权限仍然只有自己能读(rw-r--r--),多半是 umask 的问题,例子 10 会专门展开。
3.4 例子8:gpasswd 管理组内成员的生命周期与 newgrp 临时切组
用gpasswd管理组成员非常直观:
gpasswd -a user01 devteam # 添加成员 gpasswd -d user01 devteam # 移除成员 gpasswd -A user01 devteam # 设置组管理员设置组管理员后,user01 不需要 root 权限就可以自行管理devteam的成员进出,这对一线项目组自助维护很有价值,省去每次加人减人都要提审批找管理员敲命令的沟通成本。
还有一个容易被忽略的命令newgrp。当用户临时需要以某个组的身份创建文件时,可以执行:
newgrp devteam touch /data/project/testfile exitnewgrp会开辟一个子 shell,这个 shell 里进程的有效组临时变成devteam,新建文件的组归属就是devteam,用exit返回原来的 shell。这个技巧在用户主组不是 devteam、但需要偶尔向共享目录写文件时特别有用,不需要修改主组,也不会产生副作用。
4. 实战例子9-10:查询用户组信息与 umask 对组权限的隐形影响
最后两个例子侧重"看"和"防":怎么准确查询用户和组的归属关系,以及协作时最容易忽略的 umask 问题。
4.1 例子9:id、groups、getent、finger 看用户组各有侧重
查询用户属于哪些组,常用命令有四种,输出形式和使用场景不同:
id user01 groups user01 finger user01 getent passwd user01id输出最完整,直接展示 UID、主组 GID 以及所有附加组的数字和名称,排查权限问题我习惯第一眼看它。groups只显示组名列表,简洁但不显示主组和附加组的区别。finger需要额外安装,信息较全但偏"人类可读",日常诊断我并不常用。getent走的是系统名称服务接口,这台机器如果接了 LDAP 或集中认证,getent能看到远程用户,而直接grep /etc/passwd只会看本地文件,所以排查集中认证环境时必须用getent。判断"用户在不在某个组里",最可靠的还是id user01这一条。
4.2 例子10:umask 022、002、007 在共享目录里的实测差异
umask 代表创建文件时要减掉的权限位。默认一般022,所以新文件是644、新目录是755。共享目录场景下,如果组员希望互相可写,必须改变 umask 的组写权限位:
umask 002 touch /data/project/a.txt mkdir /data/project/sub ls -l /data/projectumask 002下,新文件是664(组员可写),新目录是775。如果要求组内文件完全对组可读可写、对外完全关闭,可以用umask 007,得到660和770。但注意,无论 umask 如何设置,新建的普通文件永远不会带上执行权限,这是内核的安全设计,防止误创建可执行文件。
实际协作中常见的怪象是:明明用户已经加入组、目录也配好 2770,但用户新建的文件其他组员只能看不能改。这时八成的锅就是 umask 是022,新建文件是644,同组的人只有读权限。解决办法是按共享目录的安全要求统一设置 umask,可以在/etc/profile或其profile.d下的脚本里按账号或场景设置;如果希望更精细,就该上 ACL 了,那是另一个话题。
4.3 权限排查中容易误判的两个点
第一个误判是"用户明明在组里却写不进去"。查一下id发现用户确实在组里,于是怀疑系统坏了。其实很可能是用户当前登录会话的附加组列表没有刷新,因为用户登录那一刻系统才读取组信息。解决办法是重新登录,或者用newgrp刷新当前会话的组身份,不需要重启。
第二个误判是"删了用户组还在"。企业里经常出现离职员工的主组是某个业务组,直接userdel后组还在,成员列表里还显示这个人。这不是没删干净,而是因为主组关系存在passwd文件里,删了用户不等于删了主组,删组前必须确保没有用户以它为主组。
5. 知识点提取:命令速查表与故障定位路径
这一节把全文涉及的细节集中提取成两张速查表,方便你把这篇文章当工具书用,遇到问题直接翻对应行。
5.1 核心文件与命令对照
先看四个核心文件各自记录什么,修改什么命令会影响它们:
| 文件 | 记录内容 | 典型影响命令 | 权限 |
|---|---|---|---|
| /etc/passwd | 用户名、UID、主组GID、家目录、登录shell | useradd、usermod -g、userdel | 所有用户可读 |
| /etc/shadow | 密码哈希、密码周期、账户过期信息 | passwd、chage、passwd -l/-u | root 独享 |
| /etc/group | 组名、GID、附加成员列表 | groupadd、usermod -G、gpasswd -a/-d | 所有用户可读 |
| /etc/gshadow | 组密码、组管理员 | gpasswd -A | root 独享 |
再看常用命令与操作对照,这部分是"知识点提取"的重点。
| 操作意图 | 推荐命令 | 注意事项 |
|---|---|---|
| 创建用户 | useradd 用户名 | 默认建同名组、建家目录;需要禁登录加-s /sbin/nologin |
| 设置密码 | passwd 用户名 | 加--stdin可非交互,但注意发行版兼容性 |
| 批量设初始密码 | echo "用户:密码" | chpasswd | 跨发行版通用 |
| 强制首次改密 | chage -d 0 用户名 | 配合批量建号效果好 |
| 修改主组 | usermod -g 组名 用户名 | 改 passwd 文件的 GID 字段 |
| 添加附加组 | gpasswd -a 用户名 组名 | 语义清晰,不覆盖原有列表 |
| 追加附加组 | usermod -aG 组名 用户名 | -a 必须与 -G 联用 |
| 移除附加组 | gpasswd -d 用户名 组名 | 不需要 root 也可由组管理员执行 |
| 删除用户 | userdel -r 用户名 | -r 才删家目录和邮件池 |
| 删除组 | groupdel 组名 | 该组不能是任何用户的主组 |
| 查询用户组 | id 用户名 | 主组、附加组一次看清 |
| 临时切换有效组 | newgrp 组名 | 进入子 shell,exit 返回 |
5.2 从现象到根因的快速定位
平时收到"权限不对""用户登不上"这类反馈,我的排查顺序通常是这样。
用户登录失败:先看passwd -S 用户名检查账户是否锁定,再看 shadow 文件确认是否密码过期,最后看 shell 是不是被改成了/sbin/nologin。如果是新账号首次登录要求改密,还要确认有没有执行过chage -d 0。
组权限不生效:先id 用户名确认附加组列表里有没有目标组,注意当前会话是否过期需要重登;然后看目标目录的组权限位和 setgid 位是否齐全,用ls -ld 目录一条命令就能检查drwxrws---中的s;最后看看目标目录下新建文件的组归属,如果文件组变成了用户自己的私有组,说明 setgid 没设置生效。
组删不掉:提示 "cannot remove the primary group" 时,说明有用户以这个组为主组。把这些用户的主组切到其他组,或者删除对应用户,再执行groupdel即可。
5.3 我的一点实操心得
这几个例子写下来,最深的体会是:用户和组管理出问题,九成以上不是命令不熟,而是没想清楚一条命令到底在改passwd文件,还是在改group文件。动手之前花十秒钟想想这一步会落到哪张表,很多事故都能提前避免。另外,共享目录的权限设计里,组号、setgid、umask 三者要一起考虑,少配一个都会在排查时绕远路。文中这些命令在我日常环境里反复使用,按照例子里的顺序操作,基本可以覆盖用户和组管理的绝大多数日常工作场景。