Linux 用户、用户组与附加组是系统管理里绕不开的核心主题。很多人在刚接触 Linux 时,会把注意力放在文件权限的 rwx 上,却忽略了权限的归属主体——用户和组。实际上,用户与组的配置直接决定了谁能登录系统、谁能访问某个目录、谁能执行某个需要特权的命令。这篇内容我打算把用户、用户组、附加组这几个概念串起来讲透,再用 10 个实战例子把常见的操作场景覆盖一遍。每个例子都会附带验证方法和注意事项,方便你直接照着操作。
整套内容的定位是:适合刚入门 Linux 的新手,也适合已经会基本操作但没理清主组和附加组关系的进阶学习者。读完你应该能独立完成用户创建、组管理、组权限分配、批量用户处理等任务,并且知道遇到权限问题时该从哪里排查。
1. 先把概念厘清:用户、UID/GID 与组的底层逻辑
如果你已经用了一段时间 Linux,可能知道useradd创建用户、usermod修改用户、groupadd创建组。但是很多教程只会告诉你“怎么用命令”,不会告诉你“命令到底在系统里做了什么”。这导致的问题是:一旦遇到非标准场景,就不知道怎么排查了。
1.1 用户条目到底存了哪些信息
每个 Linux 用户都对应/etc/passwd文件中的一行记录。虽然文件名里有 password 这个词,但实际密码哈希并不在这里,密码通常在/etc/shadow文件中。/etc/passwd一行由 7 个字段组成,用冒号分隔。举个实际例子:
webuser:x:1001:1001:Web Service User:/home/webuser:/bin/bash逐字段拆开看就是:
| 字段位置 | 示例值 | 含义 |
|---|---|---|
| 1 | webuser | 用户名,登录时使用的名称 |
| 2 | x | 密码占位符,x 表示密码哈希存放在 /etc/shadow |
| 3 | 1001 | UID,用户 ID,内核实际识别的是这个数字 |
| 4 | 1001 | 主组 GID,用户主组的数字 ID |
| 5 | Web Service User | GECOS 字段,通常是用户全名或备注信息 |
| 6 | /home/webuser | 家目录路径 |
| 7 | /bin/bash | 登录 Shell |
这里有一个容易忽略的知识点:内核不认用户名,只认 UID。你执行ls -l看到文件归属显示为webuser,那只是系统把 UID 翻译成了名字而已。如果两个用户拥有相同的 UID,那么在当前系统里,这两个用户名会指向同一个身份,文件的归属显示也可能出现混乱。所以在手动修改 UID 时,务必确保全局唯一。
组的信息存放在/etc/group文件中,同样是一行多个字段:
webgroup:x:1001:alice,bob这一行的含义是:组名 webgroup,密码占位符 x,GID 1001,组成员是 alice 和 bob。注意,第四列列出的成员指的是“把这个组作为附加组”的用户。如果某个用户的主组就是这个组,那么该用户不会出现在这一列,因为主组成员关系已经从/etc/passwd的第四个字段体现了。
1.2 主用户组与附加组的本质区别
很多人在这里开始犯迷糊。我见过不少初学者用usermod -g group1 user1想给用户加组,结果发现用户原来的主组被换掉了,甚至某些文件的组归属也发生了变化。
主用户组(primary group)是用户“默认”的组归属。用户创建文件时,文件所属组默认取用户的当前主组。每个用户有且仅有一个主组。
附加组(supplementary groups)是用户额外加入的组,用户可以有零个或多个附加组。附加组的主要作用是赋予用户访问特定资源的权限。比如用户 alice 是开发组成员,又是运维组成员,但她最终登录 Shell 时,当前有效组 ID 仍然是主组 ID。只有当某个文件或目录的组权限被设置为开发组或运维组可访问时,alice 才能通过附加组身份获得相应权限。
用一个通俗类比来理解:主组相当于你的“默认部门”,你写的工作文档自动盖上这个部门的章;附加组相当于你“兼任的其他部门”,那些部门的会议室和资料室也对你开放,但你日常办公还是在自己的默认部门。
下面这个例子能很直观地反映主组和附加组的区别:
# 创建一个测试用户并查看其组信息 useradd -m -d /home/alice -s /bin/bash alice id aliceid alice的输出是这样的:
uid=1001(alice) gid=1001(alice) groups=1001(alice)这里gid=后面跟的是当前有效主组,groups=后面列出的是用户所属的所有组。因为此时 alice 还没有附加组,所以 groups 里面只有 alice 组。
然后我们给 alice 添加一个附加组:
groupadd devops usermod -aG devops alice id alice输出变成了:
uid=1001(alice) gid=1001(alice) groups=1001(alice),1002(devops)注意,gid=仍然是 1001,没有变,但 groups 列表里多了一个 devops。这说明-aG只追加附加组,不会影响主组。如果你用了usermod -G devops alice(没有 -a),alice 原来的附加组会被清空,只保留 devops。
细节很重要。-a的意思是 append(追加),-G表示设置附加组列表。两者结合才是“追加附加组”;单独-G是“覆盖附加组列表”。很多线上事故都是因为少了-a导致用户丢失了原有附加组,突然无法访问某个共享目录。
2. 三剑客解析:useradd、usermod、groupadd 的设计逻辑与选择
掌握了文件层面的原理之后,理解命令的设计逻辑就水到渠成了。Linux 用户管理命令有很多,但实际高频使用的就那三四个。我按使用频率和重要程度来拆一下。
2.1 三剑客的能力边界对照表
| 命令 | 主要用途 | 常用参数 | 注意事项 |
|---|---|---|---|
| useradd | 创建新用户 | -m(创建家目录)、-d(指定家目录)、-s(指定 Shell)、-G(附加组)、-u(指定 UID) | 不加 -m 可能不创建家目录,不同发行版默认策略不同 |
| usermod | 修改已有用户属性 | -aG(追加附加组)、-g(修改主组)、-l(改名)、-L(锁定用户) | -G 不带 -a 会覆盖附加组列表 |
| groupadd | 创建新用户组 | -g(指定 GID)、-r(创建系统组) | 系统组通常用于服务账号,GID 小于 1000 |
| gpasswd | 管理组成员 | -a(添加成员)、-d(删除成员)、-M(设置成员列表) | 也可以用来设置组密码,但工作中较少用 |
| userdel | 删除用户 | -r(连同家目录和邮件目录一起删除) | 不删家目录会留下残留文件,UID 可能被复用后产生归属错乱 |
这个表格里,usermod -aG是最容易出现误操作的一对参数。我见过有人写脚本批量给用户加附加组时漏掉-a,最后所有用户的附加组都被重置成了同一个组,排查了很久才发现是参数少了一个字母。运维工作里,“小细节产生大故障”的典型案例就是这么来的。
2.2 为什么id是验证配置的第一利器
配置完用户和组之后,第一件事就是验证。验证用户组关系,id命令是最直接的:
id 用户名它会显示 uid、gid(主组)、groups(全部组)。groups 用户名也能列出该用户所属的全部组,但格式比id略简陋。
排查问题时,我一般先跑id,确认用户实际归属的所有组是否符合预期。如果预期有两个附加组,但id只显示一个,那就是添加附加组时没有用-a,把之前的覆盖掉了。这类问题用id一眼就能定位。
系统里还有一些“看起来是用户但其实是服务账号”的特殊条目,比如nobody、www-data、systemd-network等。这些账号通常没有家目录,也没有登录 Shell(/usr/sbin/nologin或者/bin/false),它们的存在是为了让某个服务以受限权限运行。你想用su切换到这些账号时通常会失败,这是正常的,因为它们被刻意设计为不可登录。理解这一点,看到/etc/passwd里一堆奇怪账号时就不会慌。
3. 10 个实战例子:从单命令操作到批量处理
这个部分是整篇内容的重点。我把 10 个例子分成两组:第一组是核心操作类,覆盖日常最常用的用户和组操作;第二组是进阶场景类,覆盖多用户协作、批量处理和特殊场景配置。
3.1 核心操作类:账号与组的基础配置
例子 1:创建用户并设置主组与附加组
先看需求:某公司新增了一名开发者,需要创建账号devel01,主组为developers,附加组为docker和sudo。
# 先创建组(如果不存在的话) groupadd developers groupadd docker # 创建用户,同时设置主组和附加组,一次完成 useradd -m -d /home/devel01 -s /bin/bash -g developers -G docker,sudo devel01 # 验证 id devel01输出会显示:
uid=1002(devel01) gid=1001(developers) groups=1001(developers),1002(docker),27(sudo)这里有两个细节。第一,-g和-G同时使用时,-g指定主组,-G指定附加组列表,多个附加组用逗号分隔,不要加空格。第二,-m和-d可以组合使用,-d指定家目录路径,-m表示如果目录不存在则创建。如果只给-d不给-m,家目录不会自动创建,用户登录后可能连 HOME 目录都没有,虽然不一定报错,但很多程序会表现异常。
例子 2:修改用户主组
场景:某员工从 A 团队调到了 B 团队,需要把主组从team_a改为team_b。
usermod -g team_b user_name执行后,该用户的主组立即变为 team_b。这里要提醒一个容易被忽略的影响:这个用户之后新建文件的默认所属组会变成 team_b,但已经存在的文件不会自动改变组归属。如果他的旧文件也需要迁移归属,通常得用find配合chown批量处理:
find /home/user_name -group team_a -exec chown :team_b {} \;这条命令把所有属于 team_a 组的文件改成 team_b 组。不过执行前最好确认你想改的范围,别把不该动的文件也改了。
例子 3:追加附加组(老生常谈的 -a 教训)
场景:当前用户alice需要临时加入project_x组才能访问某个共享目录,同时不能丢失她已有的附加组。
usermod -aG project_x alice注意,如果你写成usermod -G project_x alice,alice 原有的附加组会被全部清空,只保留 project_x。这个操作非常危险,特别是当原有附加组中包含 sudo 或 docker 这类特权组时,相当于直接移除了用户的管理和容器操作权限。
我个人的习惯是:所有涉及-G的场景,一律写成-aG,即使在“我知道用户当前只有一个附加组”的情况下也保持这个习惯。因为你可能搞不清用户被抓之后是否又有别人手动改过。宁可多花一个字母,也不要冒失配风险。
例子 4:将用户从附加组中移除
反向操作同样常见。某员工离职后需要从公司的共享协作组中移除,但保留登录账号和家目录。
gpasswd -d alice project_x执行后验证:
id alice确认 project_x 不再出现在 groups 列表中。gpasswd -d也可以换成usermod -G重新列附加组的方式移除,但gpasswd -d更精确,只操作一个组,不会影响其他成员关系,推荐优先使用。
例子 5:查看组内成员列表
想看看developers组里都有谁,最直接的方式是查看/etc/group:
getent group developers输出类似:
developers:x:1001:alice,bob,charlie但这里的第四列只显示“把 developers 作为附加组”的用户。如果某些用户的主组就是 developers,他们是不会出现在这一列里的。想拿到完整成员列表,得写个综合查询:
awk -F: '$4==1001 {print $1}' /etc/passwd; awk -F: '$1=="developers" {print $4}' /etc/group | tr ',' '\n'思路是:先从/etc/passwd中找出主组成员,再取/etc/group中附加组成员,两者合并才是真正完整的组内成员列表。实际工作中如果不需要区分主组和附加组,可以简单点:让所有人把 developers 作为附加组加入,这样getent group developers就能一网打尽。
3.2 进阶场景类:从单机管理到协作配置
例子 6:批量创建用户并设置统一附加组
某公司准备上线一个模拟项目 X,需要一次性创建 5 个账号:proj01到proj05,并且都加入project_x组,主组用project_x。
# 先创建组 groupadd project_x # 循环创建用户 for i in $(seq -w 1 5); do useradd -m -d /home/proj$i -s /bin/bash -g project_x -G project_x proj$i doneseq -w 1 5输出01 02 03 04 05,然后拼接出 proj01 到 proj05。这个脚本里我刻意把-g和-G都设成了 project_x,这样用户在/etc/passwd中的主组是 project_x,同时/etc/group中也会列出该用户,方便使用getent group project_x直观查看完整成员。
循环操作完成后用一条命令验证:
id proj01 proj02 proj03 proj04 proj05批量操作里最常犯的错是:用户名生成逻辑和预期不符。比如想生成proj1到proj5,但脚本写错用seq -w,会带前导零;想生成带前导零却漏了-w。建议先单独跑一下seq看看输出,再执行后面的循环,别在批量命令写完就裸奔。
例子 7:删除用户并完整清理文件
删除用户时,如果不加-r,用户的家目录和邮件目录会残留,时间长了可能积累大量孤儿文件。正确做法:
userdel -r proj01这条命令会删除用户和它的家目录、邮件目录,还会顺带清理/etc/passwd、/etc/shadow、/etc/group中的相关条目。但注意,userdel -r并不能删除该用户在其他目录下拥有的文件。如果用户此前在/tmp或/var/data下创建过文件,这些文件依然存在,且由于 UID 被删除,ls -l显示的将是数字 UID 而不是用户名。这种情况可以使用find扫描并处理:
find / -user 1002 2>/dev/null把输出核对一遍,再决定是删除还是chown给其他用户。强烈建议不要直接删掉所有输出,因为可能有些文件属于系统关键路径,盲目删除会引发别的故障。
例子 8:创建服务账号并禁用登录
某 Web 服务需要专属账号运行,但这个账号不能用于人肉 SSH 登录。服务账号的创建方式与普通用户有显著区别:
useradd -r -M -d /var/www/webapp -s /usr/sbin/nologin webapp参数解释:
-r:创建系统账号,UID 通常小于 1000,不进入用户“正常”范围内。-M:不创建家目录。服务账号通常不需要家目录,而是在配置里指定工作目录。-d:指定账号的主目录路径,这里指向/var/www/webapp。-s /usr/sbin/nologin:禁止该账号登录 Shell。
如果服务账号后续被某些不该用的工具调用,提示This account is currently not available,这并不是报错,而是 nologin 的效果。有些运维新手看到这个提示就以为系统出问题了,其实是账号被刻意锁定了登录路径。
例子 9:临时锁定用户而不删除
某些情况下,员工出差、项目暂停,账号需要保留但不能登录。锁定用户的方式有两种:一种是锁定密码,一种是锁定账号本身。
# 锁定密码 passwd -l username # 锁定账号(usermod 方式) usermod -L username两者效果略有差异,但核心目标都是禁止密码登录。被锁定的用户如果用 SSH 公钥登录,公钥认证此时通常仍然有效,所以如果目标是彻底禁止登录,还需要禁用公钥认证或修改其登录 Shell 为 nologin:
usermod -s /usr/sbin/nologin username解除锁定:
passwd -u username或者:
usermod -U username账号锁定是运维里非常常用的操作,但很多人只在文档里看到过,没有认真区分“锁定密码”和“禁用登录”的边界。我建议根据业务需求来决定用哪一个:只是临时不让密码登录,用passwd -l;要完全禁止该账号一切登录,直接usermod -s /usr/sbin/nologin。
例子 10:克隆用户配置并保留组关系
场景:某开发者要离职交接,新接手者希望复用原账号的完整权限配置,包括所有附加组。
思路是:先把原用户的附加组列表复制下来,再用这些组创建新用户。
# 获取原用户的附加组列表(过滤掉主组) OLD_USER=old_dev NEW_USER=new_dev OLD_GROUPS=$(id -nG $OLD_USER | tr ' ' ',' | sed "s/\b$OLD_USER\b//g" | sed 's/,,*/,/g;s/^,//;s/,$//') # 创建新用户,主组沿用原用户主组,附加组用复制来的列表 PRIMARY_GROUP=$(id -ng $OLD_USER) useradd -m -d /home/$NEW_USER -s /bin/bash -g $PRIMARY_GROUP -G "$OLD_GROUPS" $NEW_USER这里id -nG $OLD_USER列出所有组名,tr ' ' ','把空格换成逗号,再用sed去掉原用户名对应的组(因为用户名和主组同名时,该组名会出现在附加组列表里,属于冗余信息)。
这个脚本看起来繁琐,但我在实际批量交接账号时经常这么用。如果嫌sed链太长容易出错,也可以手动id看一下原用户的附加组,再手写-G参数。10 个以下用户的场景,手动其实更快。
4. 组与权限的联动机制:为什么“加了组”还是没权限
操作层面熟悉之后,必须补上权限联动这块知识。很多人明明把用户加到了正确的组,结果访问目录仍然被拒,问题往往出在文件系统的 ACL、目录权限、拥有者设置这些环节。
4.1 读、写、执行与组成员身份的关系
Linux 文件权限分三组:属主权限、属组权限、其他人权限。每个位置用rwx表示,r是读,w是写,x是执行。目录上的x比较特殊,它表示“能否进入该目录”以及“能否访问目录内文件的元数据”。
当用户访问一个文件时,系统按顺序判断:
- 如果用户是文件属主,按属主权限执行。
- 如果用户不是属主,但是文件属组的成员,按属组权限执行。
- 如果都不是,按其他人权限执行。
注意,这个判断是“先到先得”。如果用户是属主,即使属主权限没有任何 rwx,系统也会直接拒绝并结束判断,绝对不会继续看属组权限。这个逻辑非常重要,它解释了“为什么用户明明在组里却无权访问”:因为该用户恰好是这个文件的属主,而文件对属主的权限设成---了。
这种情况下用户作为属主都进不去,自然更不可能靠附加组来“救”他。实际中这种场景不多,但我在教学中见过有人把某个文件的属主改成了 root,然后给开发组加了rwx权限,结果开发用户访问时,因为文件属主是 root,开发用户不是 root,所以走的是属组权限,这才能够访问。
如果你把文件属主写错了,比如把“创建该文件的用户名”填到了另一个用户名上,那所有非属主用户都会落到“其他人”权限,而不是“属组”权限,看起来“加了组”也白搭。
4.2 虚拟项目的权限落地实战
拿一个典型的共享协作场景来演示完整链路。
假设你有一个共享目录/srv/teamshare,要让team_a和team_b两个组的成员都能读写,同时禁止其他人访问。
# 创建目录 mkdir -p /srv/teamshare # 设置属组为 team_a chown root:team_a /srv/teamshare # 设置权限:属主 root 全部权限,属组 team_a 读写执行,其他人无权 chmod 770 /srv/teamshare现在 team_a 组成员能访问,但 team_b 组成员不能访问。要让 team_b 也能访问,有两个办法。
办法一:把 team_b 的用户逐个加入 team_a 组。缺点:成员管理混乱,team_b 的用户从此有了 team_a 对所有资源的权限。
办法二:用 setgid 位 + 多组容器的思路,把共享目录的组设成一个中立的组:
groupadd share_users usermod -aG share_users user_a1 usermod -aG share_users user_b1 chown root:share_users /srv/teamshare chmod 2770 /srv/teamsharechmod 2770中的2是 setgid 位。setgid 的效果是:任何用户在这个目录下新建的文件和子目录,其属组会自动继承该目录的属组(share_users),而不是用户的主组。这样就不怕用户建文件时把组归属弄乱了。
验证 setgid 是否生效:
ls -ld /srv/teamshare输出中权限位如果是drwxrws---,s就表示 setgid 已生效。
这个时候,team_a 的用户和 team_b 的用户都通过附加组share_users获得了访问权限。他们的主组没变,但附加组统一了,文件读写也能共享。
还有一点要提:如果目录下挂载了 NFS 或者使用了 ACL,情况会更复杂。比如某目录设置了 ACL 白名单,但你的用户是通过附加组加入的,ACL 的匹配规则优先看用户本身,再看用户所属组。如果 ACL 没有覆盖到正确组,即使普通权限看起来没问题,也可能被 ACL 拒绝。总之,遇到权限问题时,先用getfacl看看有没有默认 ACL 和扩展 ACL 在干扰。
5. 高频问题与排查技巧实录:遇到权限问题先别抓瞎
最后一部分,把我工作里经常遇到的用户和组相关问题汇总一下,做成速查表形式。这些问题看起来五花八门,但根源通常集中在几个点。
| 现象 | 可能原因 | 排查方法 |
|---|---|---|
| useradd 报错 | 用户名已存在、家目录冲突、UID 冲突 | 查看 /etc/passwd 和当前用户列表 |
| 用户无法登录 SSH | 密码锁定、Shell 为 nologin、家目录权限异常 | 检查 /etc/shadow、账号锁定状态、家目录 700 权限 |
| 新创建的组在 id 里看不到 | 当前会话缓存了组信息,需要重新登录一次 | 使用 newgrp 激活新组或退出重登 |
| chown 命令提示非法用户 | 用户名不存在或拼写错误 | 检查 /etc/passwd 是否存在该用户 |
| 文件权限明明是 770,还是打不开 | 目录被 ACL 控制 | getfacl 查看 ACL |
| /etc/passwd 中出现重复 UID | 手动改坏了用户条目 | 梳理 UID 归属,避免共用关键 UID |
| userdel -r 后发现一堆无主文件 | 用户在其他目录下也有文件 | find / -uid 原UID 逐个排查 |
| 用户能访问共享目录但无法新建文件 | 目录权限不是 2770,缺 setgid 或属组不对 | 检查目录权限位和属组 |
用id定位问题是第一步。比如用户投诉访问不了某个共享路径,你在服务端执行:
id username getfacl /path/to/shared/dir ls -ld /path/to/shared/dir这三条命令基本能看出 90% 的权限问题:用户归属的组是否对口、目录的属组是否匹配、ACL 是否默认拒绝。如果还不够,再用strace -e trace=file让用户复现一次操作,看看系统在内核层面到底拒绝了哪个路径。
再补充一个使用习惯层面的建议:修改用户或组之前,先备份关键文件。/etc/passwd、/etc/shadow、/etc/group这三个文件是整个用户体系的根基,改之前用cp复制一份带时间戳的备份:
cp /etc/passwd /etc/passwd.bak.$(date +%Y%m%d%H%M%S) cp /etc/shadow /etc/shadow.bak.$(date +%Y%m%d%H%M%S) cp /etc/group /etc/group.bak.$(date +%Y%m%d%H%M%S)如果改完发现不对劲,直接恢复备份重新来。这个习惯成本极低,收益却非常大,特别是对刚开始接触用户管理的同学来说。
如果你用的是某些发行版,用户管理可能存在封装工具(比如某些桌面系统有用户管理面板),但底层逻辑还是这些文件。命令行掌握扎实了,无论用什么管理工具,心里都有底。
最后再分享一个我自己的习惯:每次配置完用户和组,我都会顺手把id输出、getent group输出和操作时间一起记录到运维笔记里。后面排查问题、审计权限、交接工作的时候,这些记录能省掉大量回忆时间。
用户与组的配置,说到底是围绕/etc/passwd、/etc/group、/etc/shadow这三个文件展开的。把底层逻辑想清楚,命令用起来就是顺理成章的事。遇到权限问题时,先看id,再看属组,最后查 ACL,按顺序来基本都能在几分钟内定位到根源。