内容纲要

🗂 | 查看 Linux 专题可浏览更多内容


文件权限(文件模式)指定谁可以在 Linux 系统上访问、改变或执行一个文件。它确保只有授权的用户和进程可以访问文件和目录。

通过命令 ls -l 就可以获得文件的权限、属主与属组等信息:

ls -l /etc/hosts
-rw-r--r-- 1 root root 186 Aug 27 18:31 /etc/hosts

这些信息表示为:

  • 文件类型及权限:-rw-r--r--;
  • 硬链接数量:1;
  • 文件属主:root;
  • 文件属组:root;
  • 文件的字节数大小:186;
  • 最后修改日期及时间:Aug 27 18:31;
  • 文件名:/etc/hosts;

属主、属组以及其他用户

对于每个文件来说,权限被分配给三种不同类别的用户:

  • 属主:文件和目录所属于的用户;
  • 属组:属于由一个或多个用户组成的组,属组用户被文件和目录的属主授予访问权。
  • 其他用户:在 Unix 术语里这叫作「世界(world)」的(即除了属主与属组外的)其他用户授权。

以 -rw-r--r-- 为例,在之前的文章有提到第一个字母表示文件类型,剩余的每三个字母分别表示属主、属组和世界(其他用户)的权限:

文件类型 属主(Owner) 属组(Group) 世界(World)
rw- r– r–

关于第一个字母表示的文件类型:

  • 普通文件:-
  • 目录:d
  • 字符设备文件:c
  • 块设备文件:b
  • 本地域套接字:s
  • 具名管道(FIFO):p
  • 符号链接:l

读取、写入和执行

举个例子,-rwxr-r-- 表示这是一个普通文件,属主拥有读取 r(ead)、写入 w(rite) 及执行 x(ecute) 权限,而属组和世界(其他用户)均有读取但没有写入和执行权限。

权限 文件 目录
r(ead) 允许打开和读取文件 允许列出目录文件(需同时含有执行权限)
w(rite) 允许将文件写入或截断,但不允许重命名或删除文件(由目录属性决定)。 允许在目录内创建、删除及重命名文件(需同时含有执行权限)
x(ecute) 允许将文件视为程序并执行,用脚本语言编写的程序文件也必须设置为可读才能执行 允许进入该目录
无权限 无权限

💡 你可能对属组还不是很明白,举个例子有这么一个目录:

drwxr-x--- 3 a www 4096 Mar 30 15:30 wwwroot

属主是用户 a,属组是 www。这时用户 b 没有权限的,因为其他用户权限是 ---。
但让用户 b 加入到名为 www 到用户组就可以拥有权限 r-x。

💡思考

那么当一个文件的属主没有写入权限而属组有写入权限时,如:

-----w---- 2 user1 group1 4.0K Aug 23 08:45 README

注意 user1 用户属于 group1 用户组,这时可以写入吗?

这是一个权限冲突的问题,属主权限是 ---,属组权限是 -w-,当遇到这样的情况时以属主权限为准,即便该用户属于有写入权限的用户组。

修改文件权限:chmod

**文件和目录只有其属主或 root 用户通过 chmod(Change Mode)命令修改文件或目录的文件模式(权限)。

chmod 命令有八进制表示法和符号表示法两种模式表示方式。

八进制表示法

八进制 二进制 模式
0 000
1 001 –x
2 010 -w-
3 011 -wx
4 100 r–
5 101 r-x
6 110 rw-
7 111 rwx

举个例子:

mkdir Documents && ls -l
drwxr-xr-x 2 conners conners 4096 Apr  8 11:32 Documents

这里新建了一个名为 Documents 的目录,并且可以看到权限是 rwxr-xr-x。

按照八进制表示法就是 755,也就是依照 3 个八进制数位表示属主、属组与其他用户的权限。

💡 7 = 4(r) + w(2) + x(1)

那么如果想要将其他用户的权限禁止掉,即修改为 rwxr-x--- 就可以使用八进制表示法:

# chmod <八进制表示法> <目标文件或目录>
chmod 750 Documents

符号表示法

符号 含义
u 属主(user 缩写)
g 属组(group 缩写)
o 其他用户(other 缩写)
a 所有(all 缩写,等同于 u、g、o 一起设置)
+ 添加权限
移除权限
= 只赋予指定权限并移除其他权限

举几个例子:

# 为指定文件或目录的属组添加写入权限
chmod g+w <目标文件>

# 移除属主、属组与其他用户的写入权限
# 可以进一步简写为 -w 省去 a
chmod a-w <目标文件>

# 可以同时添加多个权限,如为属组同时添加写入与执行权限
chmod g+wx <目标文件>

# 等号会移除其他权限
# 如下,为属组与其他用户设置为仅有读取与执行权限,如果原有写入权限会被禁用
chmod go=rw <目标文件>

# 一次性为属主、属组与其他用户设置不同权限时,可用英文逗号隔开
chmod u+x,go=rx <目标文件>

💡 可以使用 -v 选项得出修改前后的差别及八进制表示:

chmod -v 755 file.txt
mode of 'file.txt' changed from 0644 (rw-r--r--) to 0755 (rwxr-xr-x)

复制已有文件的权限

可以使用 --reference 选项复制已有文件的权限:

# chmod --reference=<被复制权限文件> <复制权限文件>
chmod --reference=a.sh b.sh

如上,文件 b.sh 就拥有了和 a.sh 一样的文件权限。

递归设置子目录文件权限

chmod 还可以使用 -R 选项以递归的方式更新目录内所有文件的权限。但注意目录下的内容及子目录未必都是相同的属性,比如有的是可执行文件有的是文本文件。

在使用 chmod -R 进行执行权限调整需要谨慎,它不会去管执行权限对目录和普通文件有不同的含义。前面说过对于目录 x 表示进入目录,对于普通文件 x 表示执行。

针对这个问题,也就是如果只想对文件进行一个递归设置,但目录不要进行相应权限设置,可以搭配 find 命令:

find ~ -type f -exec chmod 640 {} \;

上面这条命令表示使用 fimd 查找主目录(~)下的普通文件(-type f),执行命令(-exec),命令为 chmod 640,针对查找到的文件({}),命令结束(\;)

修改文件属主与属组:chown 与 chgrp

使用 chown 和 chgrp 命令可以用于改变文件的属主和属组。

只有 root 用户可以改变文件的属主,普通用户只有在拥有该文件并且只能更改为其所属的组时才能更改该文件的属组,而 root 用户可以更改所有文件的属组。

修改文件的属主:

sudo chown <目标用户> <文件名>

在用户组前加上冒号即为修改文件的属组,属主不变:

chown :<目标群组> <文件名>

将两者结合,修改目录 wwwroot 的属主为 nginx,属组为 web:

# -R 选项用于递归修改目录下的子文件及子目录
sudo chown -R nginx:web wwwroot

除了使用 chown :<目标属组> <文件名>,使用 chgrp 命令也可以达到相同效果:

sudo chgrp <目标群组> <文件名>

特殊权限

除了读取、写入、执行权限之外还有其他一些较少用到的特殊权限设置。

SUID(Set User ID)

ls -l /usr/bin/passwd /etc/passwd /etc/shadow
-rw-r--r-- 1 root root    1443 Apr  7 22:39 /etc/passwd
-rw-r----- 1 root shadow   974 Apr  7 22:39 /etc/shadow
-rwsr-xr-x 1 root root   63960 Feb  7  2020 /usr/bin/passwd

在使用 passwd 命令(/usr/bin/passwd)修改密码时,会修改存储密码的配置文件 /etc/shadow(在之前的文章有提到,历史上密码存储于 /etc/passwd 中但现在已经不是了)。

但只是普通用户权限的我们是如何得以修改 /etc/shadow 文件的?答案是 setuid。

setuid 位(八进制表示为 4000),如果将其应用于可执行文件,会将有效用户ID(effective user ID)从真实用户(实际执行程序的用户)ID更改为程序属主的有效用户ID。

大多数情况下,少数超级用户的程序才会做此设置。当普通用户执行 setuid root 程序时,该程序将以超级用户的权限来执行,可以访问普通用户通常被禁止访问的文件和目录。这显然会带来安全隐患,因此允许设置 setuid 位的程序数量必须控制在最小范围内。

chmod u+s <程序文件名>

# 八进制法表示为
chmod 4755 <程序文件名>

权限:-rwsr-xr-x,注意属主的s。

SGID(Set Group ID)

setgid 位类似于 setuid 位,它会将有效组ID(effective group ID)从真实用户的真实组 ID(real group ID)更改为文件属主的有效组 ID。

如果对目录设置 setgid 位,那么在该目录下新创建的文件将由该目录的属组所有,而非文件创建者的属组所有。

这在共享目录中非常有用,当一个共同属组的成员需要访问目录中的所有文件时,不用管文件属主的属组是什么。

chmod g+s <目录名>

# 八进制法表示为
chmod 2775 <目录名>

权限:drwxrwsr-x,注意属组的 s。

Sticky Bit

粘滞(sticky)位来自「远古」Unix 的遗留产物,用于将可执行文件标记为「不可交换」。

Linux 会忽略文件上设置的粘滞位,如果对目录设置了粘滞位,则能够阻止用户删除或者重命名其中的文件,除非用户是该目录的属主,或者是文件的属主,又或者是超级用户。

粘滞位常用来控制对共享目录(如 /tmp)的访问。

chmod +t <目录名>

# 八进制法表示为
chmod 1777 <目录名>

权限:drwxrwxrwt,注意最后的t。

默认权限:umask

一般来说 Linux 上的文件或目录使用一组默认的权限进行创建,umask 命令可用于查看或设置创建新文件的时的默认权限。

不带任何选项参数的情况下使用 umask 命令就是查看当前的掩码值:

umask
0022

可以看到当前的掩码值是 0022,在一些发行版中也常见为 0002

然后使用 touch 和 mkdir 创建一个文件和目录再看看它们的权限是怎样的:

touch file.txt
mkdir dir

ls -l
drwxr-xr-x 2 toor toor 4096 Sep  2 14:46 dir
-rw-r--r-- 1 toor toor    0 Sep  2 14:46 file.txt

也就是说这些权限是默认值减去 umask 掩码值得到的:

  • 目录 dir:0777 – 0022 = 755(rwxr-xr-x);
  • 文件 file.txt:6000 – 0022 = 644(rw-r–r–);

此时我想要更改默认权限,就可以反推一下进行设置掩码值,如:

umask 0002 # 之前是 0022

touch test.txt

ls -l test.txt
-rw-rw-r-- 1 toor toor 0 Sep  2 14:52 test.txt

可以看到新创建的文件默认权限已经变成了 664(rw-rw-r–)

使用 umask 掩码值并不会改变之前文件的权限,而且仅临时生效于当前 shell 会话,如果想要永久生效需要修改 shell 的配置文件。