这一篇讲什么
用户和权限的命令都很简单,坑在于同一条命令,三个发行版的默认行为不一样 ,以及改完了,但对已经在跑的东西不生效。本篇在三台机器上逐个复现,贴原始输出。
机器:CentOS 7.9(VMware)、Rocky 9.8、Ubuntu 24.04.5(后两台是 LXD 虚拟机,cloud 镜像)。
1. useradd:Ubuntu 上不建家目录,shell 是 sh ✅
同样一条 useradd labu1,不带任何参数:
ruby
labu1:x:1000:1000::/home/labu1:/bin/bash (Rocky 9)
drwx------. 2 labu1 labu1 4096 Sep 21 17:42 /home/labu1
labu1:!!
labu1:x:1000:1000::/home/labu1:/bin/sh (Ubuntu 24.04)
ls: cannot access '/home/labu1': No such file or directory
labu1:!
CentOS 7 和 Rocky 9 一样。Ubuntu 上:
- 不建家目录 ------
/etc/passwd里写着/home/labu1,目录却不存在; - shell 是
/bin/sh(Ubuntu 上它是 dash,不是 bash)。
原因在默认配置里:
ini
HOME=/home
SHELL=/bin/sh (Ubuntu 24.04,useradd -D)
CREATE_HOME yes (Rocky 9 的 /etc/login.defs;Ubuntu 没有这一行)
跨发行版的脚本,一律写全:useradd -m -s /bin/bash。
另外,adduser 在 CentOS 7 / Rocky 9 上只是个软链:
bash
lrwxrwxrwx. /usr/sbin/adduser -> useradd
Ubuntu 上 adduser 是一个独立的交互式程序,会建家目录、问密码。
2. passwd --stdin:Ubuntu 上没有 ✅
脚本里批量设密码的常见写法:
sql
passwd: all authentication tokens updated successfully. (CentOS 7 / Rocky 9)
passwd --stdin 退出码=0
passwd --stdin 退出码=6 (Ubuntu 24.04)
--stdin 是 RHEL 系 passwd 的扩展。通用写法是 chpasswd,三台都成功:
chpasswd 退出码=0
3. usermod -G 忘了 -a,原来的组全没了 ✅
ini
加两个组后:uid=1000(labu1) gid=1000(labu1) groups=1000(labu1),1002(labg1),1003(labg2)
usermod -G(不带 -a)后:uid=1000(labu1) gid=1000(labu1) groups=1000(labu1),1004(labg3)
三台一样。-G 是「把附加组设置成 这些」,不是「加上这些」。labg1、labg2 被清掉了 ------ 如果被清掉的是 wheel 或 sudo,这个用户就没有 sudo 权限了。永远写 usermod -aG。
4. 加了组,已经在跑的进程不认 ✅
usermod -aG labg4 labu1 之后:
bash
id labu1:labu1 labg3 labg4
已在运行的进程(974)的组:Groups: 1000 1004
labg4 的 GID=1005
新登录 su - labu1 -c id:labu1 labg3 labg4
三台一样。id labu1 查的是配置,显示已经有 labg4 了;但已经在运行的进程 (看 /proc/<PID>/status 的 Groups),组列表是登录那一刻定下来的,里面没有 1005。
实际影响:把运行服务的用户加进某个组,服务要重启才能用上新组的权限 ;给自己加了 docker 组,要重新登录。
5. su 和 su -,PATH 不一样 ------ 但 Ubuntu 例外 ✅
从 PATH=/usr/bin:/bin 出发切到 root:
ruby
su: PATH=/usr/bin:/bin 目录=/root (Rocky 9)
su -: PATH=/root/.local/bin:/root/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin 目录=/root
su(不带 -)保留原来的 PATH,里面没有 /usr/sbin ,于是 useradd、ip、sysctl 这类命令会报「command not found」。CentOS 7 同样(su 后仍是 /usr/bin:/bin)。
Ubuntu 24.04 上 su 不带 - 也给了完整的 PATH:
ruby
su: PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin 目录=/root
所以同一个问题,在 Ubuntu 上复现不出来。要一个干净的 root 环境,用 su -。
6. sudo 权限:wheel 还是 sudo 组 ✅
scss
%wheel ALL=(ALL) ALL (CentOS 7 / Rocky 9)
#includedir /etc/sudoers.d
%sudo ALL=(ALL:ALL) ALL (Ubuntu 24.04)
@includedir /etc/sudoers.d
给用户 sudo 权限:RHEL 系加进 wheel,Ubuntu 加进 sudo。#includedir 开头的 # 不是注释 ,是老语法(新版 sudo 改成了 @),别删。
7. sudoers.d 里的文件名带点,整个文件被忽略 ✅
在 /etc/sudoers.d/ 里放一个规则,文件名分别叫 labu1.conf 和 labu1:
less
[文件名 labu1.conf] sudo: a password is required
[文件名 labu1] 0
三台一样(CentOS 7 那台是中文提示「sudo: 需要密码」)。文件名里带 . 的文件会被跳过 ,不报错、不警告,规则就是不生效。习惯了 xxx.conf 的人最容易踩。
8. 往 sudoers.d 里放一个语法错误的文件 ✅
不用 visudo、直接写一个有语法错误的文件(NOPASSWD 后面少了冒号):
css
labu2 ALL=(ALL) NOPASSWD ALL
CentOS 7:root 自己的 sudo 都用不了了:
bash
[有一个坏文件时 root 用 sudo] >>> /etc/sudoers.d/labbad: syntax error near line 1 <<< sudo: parse error in /etc/sudoers.d/labbad near line 1 sudo: no valid sudoers sources found, quitting sudo: unable to initialize policy plugin
普通用户 labu1 那条正常规则也一起失效。如果这台机器 root 不能直接登录、只能靠 sudo,你就把自己锁在外面了。
Rocky 9(sudo 1.9.17p2)/ Ubuntu 24.04(sudo 1.9.15p5):只报错,sudo 照样能用:
sql
/etc/sudoers.d/labbad:1:26: syntax error
labu2 ALL=(ALL) NOPASSWD ALL
^~~
visudo -c 退出码=1
[有一个坏文件时 root 用 sudo] /etc/sudoers.d/labbad:1:26: syntax error labu2 ALL=(ALL) NOPASSWD ALL ^~~ 退出码=0
新版 sudo 跳过坏的那一条,其余规则继续生效,还标出了出错的位置。但别指望这个 ------ 老系统上就是全挂。改 sudoers 一律用 visudo(它保存前会检查语法),改 sudoers.d 下的文件用 visudo -f /etc/sudoers.d/xxx,改完 visudo -c 再检查一遍。
9. SGID 共享目录:设了还是不能一起改 ✅
经典做法:建一个 labproj 组,目录属组设成它,加上 SGID(2775),这样组里的人新建的文件自动属于 labproj:
bash
drwxrwsr-x 2 root labproj 4096 Sep 21 17:42 /data/labproj
labu1 建两个文件,labu2(同组)去追加内容:
CentOS 7 / Ubuntu 24.04(登录用户的 umask 是 0002):
less
labu1 的 umask:0002
-rw-rw-r-- 1 labu1 labproj 8 Sep 21 17:42 a.txt
-rw-r--r-- 1 labu1 labproj 8 Sep 21 17:42 b.txt
u2 追加 a.txt 成功
-bash: line 1: /data/labproj/b.txt: Permission denied
b.txt 是用 umask 022 建的,组没有写权限。
Rocky 9:
diff
labu1 的 umask:0022
-rw-r--r--. 1 labu1 labproj 8 Sep 21 17:42 a.txt
-rw-r--r--. 1 labu1 labproj 8 Sep 21 17:42 b.txt
-bash: line 1: /data/labproj/a.txt: Permission denied
-bash: line 1: /data/labproj/b.txt: Permission denied
Rocky 9 的普通用户默认 umask 就是 0022,新建的文件组只读,两个都改不了。
结论:SGID 只管「文件属于哪个组」,不管「组有没有写权限」。 要真正共享写,还要保证新文件的组有写权限 ------ 上面 CentOS 7 / Ubuntu 的 a.txt 就是 umask 002 建出来、能共享写的样子。
去掉 SGID 后,新文件的属组变回用户自己的组:
bash
-rw-rw-r-- 1 labu1 labu1 2 Sep 21 17:42 /data/labproj/c.txt
10. 文件是 644,却读不了 ------ 看路径上每一层目录 ✅
f.txt 是 644,所在目录 sub 是 700(属主 root):
bash
cat: /data/labperm/sub/f.txt: Permission denied
namei -l 把路径上每一层的权限都列出来:
lua
f: /data/labperm/sub/f.txt
drwxr-xr-x root root /
drwxr-xr-x root root data
drwxr-xr-x root root labperm
drwx------ root root sub
-rw-r--r-- root root f.txt
一眼看出卡在 sub。访问一个文件,要对路径上每一层目录都有 x 权限。
目录有 r 没有 x(744)时,更诡异:
ruby
f.txt
ls: cannot access '/data/labperm/sub/f.txt': Permission denied
total 0
-????????? ? ? ? ? ? f.txt
cat: /data/labperm/sub/f.txt: Permission denied
(Ubuntu 24.04,三台一样。)ls 能列出文件名(r 管读目录内容),但 ls -l 看不到属性、cat 读不了(x 管进入目录访问里面的文件)。
11. 删文件看的是目录权限,不是文件权限 ✅
在一个 777 的目录里,labu2 建了一个只读文件(444),labu1 去删:
bash
-r--r--r-- 1 labu2 labu2 3 Sep 21 17:42 /data/labdel/u2.txt
labu1 删掉了 labu2 的只读文件
三台一样。删除是修改目录,和文件本身的权限无关。 这就是 /tmp 要加粘滞位(t)的原因:
bash
drwxrwxrwt 2 root root 4096 Sep 21 17:42 /data/labdel
rm: cannot remove '/data/labdel/u2b.txt': Operation not permitted
加上 +t 后,只有文件属主(和 root)能删。
12. chmod -R 755,把文件也变成了可执行 ✅
ini
-rwxr-xr-x 1 root root 0 Sep 21 17:42 a.conf
drwxr-xr-x 2 root root 4096 Sep 21 17:42 d
== u=rwX,go=rX:
-rw-r--r-- 1 root root 0 Sep 21 17:42 a.conf
drwxr-xr-x 2 root root 4096 Sep 21 17:42 d
chmod -R 755 让配置文件都带上了 x。用大写的 X :只给目录(和本来就有 x 的文件)加执行权限。
13. cp 丢了属主和时间 ✅
root 复制一个属于 labu1、日期 2020-01-01 的文件:
bash
-rw-r----- root root 2026-09-21 /data/labcp/c1
-rw-r----- labu1 labu1 2020-01-01 /data/labcp/c2
-rw-r----- labu1 labu1 2020-01-01 /data/labcp/owned
c1 是普通 cp:属主变成 root、时间变成今天。c2 是 cp -a:原样保留。用 root 复制别人的文件或网站目录,不加 -a,程序就读不了了。
目标目录已存在时,cp -r src dst 的结果是 dst/src/,不是把 src 的内容放进 dst:
bash
/data/labcp/dst
/data/labcp/dst/src
/data/labcp/dst/src/f
14. ln -sf 换不掉指向目录的软链 ✅
cur 是指向 v1 目录的软链,想把它改成指向 v2:
bash
ln -sf 之后 cur -> /data/labln/v1;v1 里多了:v2
ln -sfn 之后 cur -> /data/labln/v2
三台一样。ln -sf v2 cur 把 cur 当成了目录,在 v1 里面建了一个叫 v2 的软链 ,cur 本身没变。发布系统用软链切换版本时,这个错误会让你以为切过去了。要加 -n。
15. find | xargs rm 遇到带空格的文件名 ✅
less
xargs 之后剩:a b.log
-print0 之后剩:0 个
a b.log 被 xargs 拆成了 a 和 b.log 两个参数,都不存在,文件留了下来(如果恰好有叫 a 的文件,删的就是它)。用 find ... -print0 | xargs -0,或者直接 find ... -delete。
16. root 也删不掉的文件 ✅
bash
rm: cannot remove '/data/labcp/imm': Operation not permitted
rm 退出码=1
----i---------e------- /data/labcp/imm
chattr -i 后删掉了
权限看着正常、root 也删不掉,用 lsattr 看有没有 i(不可修改)属性。
17. rm -rf "$DIR"/ 的保险 ✅
shell
# DIR=; echo rm -rf "${DIR:?}"/x; echo "这一行不会执行"
bash: line 1: DIR: parameter null or not set
变量为空时,${DIR:?} 直接报错退出,后面的命令都不会执行。脚本里 rm -rf "$DIR/" 一旦 DIR 为空就是 rm -rf /,删除类命令里的变量一律写成 ${DIR:?}。
18. userdel 不带 -r,留下一堆无主文件 ✅
bash
drwxr-x--- 2 1001 1001 4096 Sep 21 17:42 /home/labu2
/home/labu2
/home/labu2/.profile
/home/labu2/.bash_logout
用户删了,家目录还在,属主显示成数字 1001。以后新建的用户如果拿到了 1001 这个 UID,这些文件就自动归他了。 用 find / -nouser 找出来处理掉。
另外两个小差异:
rename:CentOS 7 / Rocky 9 自带(util-linux 版,rename .txt .bak *.txt);Ubuntu 默认没有。locate:三台都没装。
小结
| 坑 | 对策 |
|---|---|
| Ubuntu useradd 不建家目录、shell 是 sh | useradd -m -s /bin/bash |
Ubuntu 没有 passwd --stdin |
用 chpasswd |
usermod -G 清掉原有组 |
usermod -aG |
| 加组后服务没权限 | 重启服务 / 重新登录 |
su 后找不到命令 |
su - |
| sudoers.d 规则不生效 | 文件名不能带 . |
| 一个坏文件让 sudo 全挂(CentOS 7) | 用 visudo -f |
| SGID 目录还是改不了别人的文件 | 保证 umask 是 002 |
| 644 的文件读不了 | namei -l 看每层目录 |
chmod -R 755 文件都可执行 |
用 u=rwX,go=rX |
| 软链切换没切过去 | ln -sfn |
下一篇:文本处理、压缩与传输 ------ sed -i 会破坏软链、tar -cfz 生成一个叫 z 的文件、scp 在新版本上已经不走 scp 协议。