chmod 755 与 u+x:数字和符号权限模式
了解 chmod 的八进制数字与 u/g/o 符号写法,为什么递归使用数字模式会破坏目录树,以及大写 X 如何只为目录和已有执行权限的文件添加执行位。
chmod 的作用
chmod 用于修改文件或目录的权限位,并支持两种表示法。数字形式使用八进制数字,一次性替换所有权限位。符号形式指定用户类别和操作符,只修改明确指定的权限位。两种形式最终都会调用同一个系统调用。区别在于,未明确指定的权限位会如何处理。
读取权限模式和修改权限模式是两项不同的操作。如果您还不熟悉长格式列表中的权限字母,请先阅读逐字段了解 drwxr-xr-x 的含义。本指南只介绍如何修改这些权限位。
以下所有操作都应使用普通登录用户执行。root 账户可以绕过这些检查,因此使用 root 运行权限演示没有意义。如果您仍在新服务器上使用 root 工作,请创建具有最小权限的普通用户,然后再回来。
建立一个可随时删除的沙盒
cd "$(mktemp -d)"
touch notes.txtmktemp -d 会在 /tmp 下创建一个空目录,并输出创建的路径;命令替换 会将该路径直接传递给 cd。下面的操作不会接触任何重要文件。
一条命令会报告每次修改的结果:
stat -c '%a %A %n' notes.txt%a 以八进制数字输出模式,%A 以长列表使用的字母形式输出相同模式,%n 输出名称。在下面的代码块中,以 $ 开头的行是您输入的内容,其下一行是命令输出的内容。请自行运行这些命令。您计算机上的输出应逐行一致,因为数字模式不依赖于环境中的任何内容。
数字形式:每个类别使用一个八进制数字
第一个数字属于文件所有者。第二个数字属于文件所属组。第三个数字属于其他所有用户。每个数字都是各个位的值之和。一次设置一个位,然后查看 stat 报告的结果。
$ chmod 400 notes.txt
$ stat -c '%a %A %n' notes.txt
400 -r-------- notes.txt
$ chmod 200 notes.txt
$ stat -c '%a %A %n' notes.txt
200 --w------- notes.txt
$ chmod 100 notes.txt
$ stat -c '%a %A %n' notes.txt
100 ---x------ notes.txt字母与数字一一对应。忽略开头表示文件类型的字符后,前 3 个字母属于所有者,中间 3 个属于所属组,最后 3 个属于其他所有用户。将各个位的值相加,即可组合同一类别中的多个位。
$ chmod 600 notes.txt
$ stat -c '%a %A %n' notes.txt
600 -rw------- notes.txt
$ chmod 700 notes.txt
$ stat -c '%a %A %n' notes.txt
700 -rwx------ notes.txt
$ chmod 750 notes.txt
$ stat -c '%a %A %n' notes.txt
750 -rwxr-x--- notes.txt数字模式是绝对模式。每次运行都会写入全部 9 个位,因此即使某个类别此前并未考虑,它的权限位也会被写入。
$ chmod 755 notes.txt
$ stat -c '%a %A %n' notes.txt
755 -rwxr-xr-x notes.txt
$ chmod 600 notes.txt
$ stat -c '%a %A %n' notes.txt
600 -rw------- notes.txt第二条命令没有指定所属组或其他所有用户,但它们的权限位仍然发生了变化。这正是数字形式的特征,也是数字形式不适合进行小范围修改的原因。(chmod 还可以处理 setuid、setgid 和 sticky 位;这些位位于上述数字之前,使用第 4 个数字表示。这是另一个主题,本指南只介绍这 3 类权限位。)
符号形式:先写类别,再写运算符,最后写权限位
先写类别。u 表示所有者,g 表示组,o 表示其他所有用户,a 表示以上全部类别。然后写运算符:+ 添加权限位,- 移除权限位,= 将该类别设置为所列出的权限位,并清除该类别的其他权限位。最后写权限位本身,即 r、w 或 x。
$ chmod 640 notes.txt
$ stat -c '%a %A %n' notes.txt
640 -rw-r----- notes.txt
$ chmod u+x notes.txt
$ stat -c '%a %A %n' notes.txt
740 -rwxr----- notes.txt
$ chmod g-r notes.txt
$ stat -c '%a %A %n' notes.txt
700 -rwx------ notes.txt
$ chmod o=r notes.txt
$ stat -c '%a %A %n' notes.txt
704 -rwx---r-- notes.txt每条命令只处理一个类别,其他类别保持不变。使用逗号连接多个子句,中间不要加空格;chmod 会从左到右依次应用这些子句。
$ chmod u=rw,go=r notes.txt
$ stat -c '%a %A %n' notes.txt
644 -rw-r--r-- notes.txt
$ chmod go= notes.txt
$ stat -c '%a %A %n' notes.txt
600 -rw------- notes.txtgo= 等号后不写任何内容时,会移除组和其他所有用户的全部权限位。对于只有所有者可以读取的文件,应使用这种形式,例如 SSH 密钥对 的私钥部分。
chmod 还可以报告自身执行的操作。在无人查看屏幕的脚本中,这一点很重要。
chmod -v u+x notes.txt
chmod -v u+x notes.txt-v 会为传入的每个文件输出一行。运行这两条命令并比较屏幕上的两行:第一条会列出文件名,并报告权限模式修改前后的状态;第二条会报告权限模式未发生变化,因为它尝试添加的权限位已经设置。-c 仅在文件实际发生变化时输出一行,因此更适合写入日志。
chmod 755 与 u+x:应使用哪种表示法
从同一个文件开始,分别执行两次,然后比较每种表示法留下的权限状态。
$ chmod 640 notes.txt
$ chmod 755 notes.txt
$ stat -c '%a %A %n' notes.txt
755 -rwxr-xr-x notes.txt
$ chmod 640 notes.txt
$ chmod u+x notes.txt
$ stat -c '%a %A %n' notes.txt
740 -rwxr----- notes.txt两条命令都为文件所有者添加了执行权限。只有其中一条保留了其他两个类别原有的权限。明确知道所需的最终状态并希望强制执行时,应使用数字模式。在从未接触过的机器上也必须产生相同结果的部署脚本中,这种做法更可靠。若你的要求包含“添加”或“删除”,应使用符号模式,因为数字形式无法表达“保留该部分不变”。
新文件的初始模式并不是由 chmod 设置的,而是由创建文件的进程的 umask 决定的。在自己的 shell 中运行 umask 和 umask -S,查看当前生效的值。不同发行版、登录 shell 与 systemd 服务,以及不同账户之间的值都可能不同。umask 设置每个新文件的模式,因此 chmod 运行前,umask 决定了文件的初始状态。
目录的 execute 位有什么作用
这是最容易造成时间浪费的误解。在普通文件上,execute 位表示内核可以执行该文件。目录不包含指令,因此其中没有可执行的内容。对目录而言,execute 位表示遍历权限:允许解析目录中的名称。要访问 dir/file,需要在 dir 上拥有该权限。read 位是独立的权限,允许列出目录中包含的名称。两者可以单独存在,实际观察过这种情况后就更容易理解。
$ mkdir vault
$ printf 'hello\n' > vault/secret.txt
$ chmod 600 vault/secret.txt
$ chmod 100 vault
$ echo vault/*
vault/*
$ cat vault/secret.txt
helloShell 原样输出了该模式,因为展开 vault/* 需要读取目录,而不匹配任何内容的 glob 模式会作为字面文本传递,而不是引发错误。cat仍然成功,因为您自行提供了名称,并且拥有遍历权限。现在交换这两个权限位。
$ chmod 400 vault
$ echo vault/*
vault/secret.txt
$ cat vault/secret.txt
cat: vault/secret.txt: Permission denied名称可见,但内容不可访问。Read 提供名称列表。Execute 提供进入目录的权限。您使用的几乎每个目录都需要同时具备这两项权限。
$ chmod 500 vault
$ cat vault/secret.txt
hello为什么使用带数字模式的 chmod -R 会破坏目录树
-R 会遍历整个目录树,并将指定的模式应用到遇到的所有对象。目录和数据文件需要不同的权限位,因此同一个绝对数字不可能同时适用于两者。先创建一个小型目录树,观察错误是如何发生的。
$ mkdir -p site/css
$ touch site/index.html site/css/main.css
$ printf '#!/bin/bash\necho hi\n' > site/deploy.sh
$ chmod 755 site site/css site/deploy.sh
$ chmod 644 site/index.html site/css/main.css
$ stat -c '%a %A %n' site site/css site/index.html site/css/main.css site/deploy.sh
755 drwxr-xr-x site
755 drwxr-xr-x site/css
644 -rw-r--r-- site/index.html
644 -rw-r--r-- site/css/main.css
755 -rwxr-xr-x site/deploy.sh这就是静态文件目录所需的布局。下面是第一个递归错误。
$ chmod -R 755 site
$ stat -c '%a %A %n' site/index.html site/css/main.css
755 -rwxr-xr-x site/index.html
755 -rwxr-xr-x site/css/main.css没有任何内容损坏,而问题就在这里。这个错误不会显示出来,因此多年来一直存在于复制的操作说明中。现在有两个数据文件被标记为可执行文件。Git 会保存执行位,因此下一次提交会将这一变化带给所有克隆该仓库的人,而 rsync 和 tar 会将它带到你复制该目录树的每台机器上。
第二个递归错误会直接导致问题。将不包含执行位的模式应用到目录,目录就会无法访问。
$ chmod 644 site
$ stat -c '%a %A %n' site
644 drw-r--r-- site
$ echo site/*
site/css site/deploy.sh site/index.html
$ cd site
bash: cd: site: Permission denied
$ cat site/index.html
cat: site/index.html: Permission denied由于读取位仍然存在,名称仍会列出;但由于执行位被移除,穿过该目录的所有路径都被阻断。chmod -R 644 site 会在一条命令中对目录树中的每个目录执行此操作。之后看到的结果也不一定完全一致,因为 chmod 必须遍历它正在移除遍历权限的同一批目录,所以应使用 find site -type d -exec stat -c '%a %n' {} + 检查实际生效的结果,不要想当然。继续之前,先将其恢复。
$ chmod 755 site
$ chmod 644 site/index.html site/css/main.css
$ stat -c '%a %A %n' site site/index.html
755 drwxr-xr-x site
644 -rw-r--r-- site/index.html递归 chmod 没有撤销功能。chmod 不会记录它替换了哪些权限,因此只能根据你认为原本的权限重新构建并修复。这与 意外执行 rm -rf 后重建文件 一样缓慢且不确定,教训也相同:执行递归命令前,先检查命令的目标路径。
大写 X:只对目录设置执行权限,不要对每个文件都设置
符号模式既接受 X,也接受 x。X只在适用的对象上设置执行位:目录,或已经为某个用户类别设置执行位的普通文件。这正是您希望 -R遵循的规则。
$ chmod -R u=rwX,go=rX site
$ stat -c '%a %A %n' site site/css site/index.html site/css/main.css site/deploy.sh
755 drwxr-xr-x site
755 drwxr-xr-x site/css
644 -rw-r--r-- site/index.html
644 -rw-r--r-- site/css/main.css
755 -rwxr-xr-x site/deploy.sh只需一条命令,目录仍可遍历,数据文件则保持不变。脚本原本已有执行位,因此保留了该权限,这正是 X规则的另一部分。X读取文件当前的权限模式,因此已经丢失所有执行位的文件无法通过它恢复执行权限。
$ chmod 644 site/deploy.sh
$ chmod -R u=rwX,go=rX site
$ stat -c '%a %A %n' site/deploy.sh
644 -rw-r--r-- site/deploy.sh
$ site/deploy.sh
bash: site/deploy.sh: Permission denied
$ chmod u+x site/deploy.sh
$ stat -c '%a %A %n' site/deploy.sh
744 -rwxr--r-- site/deploy.sh
$ site/deploy.sh
hi反过来也适用同一规则。X会将文件已有的执行位扩展到另一个用户类别:
$ chmod go+X site/deploy.sh
$ stat -c '%a %A %n' site/deploy.sh
755 -rwxr-xr-x site/deploy.sh因此,小写 x用于指定一个要设置为可执行的文件,而 X用于遍历整个目录树。如果您需要为不同类型的对象设置精确的权限模式,而不是根据当前模式应用规则,请改用 find拆分遍历。
find site -type d -exec chmod 755 {} +
find site -type f -exec chmod 644 {} +
chmod u+x site/deploy.sh-type d选择目录,-type f选择普通文件,因此每种对象都会获得应有的权限模式。-exec ... +会将多个路径批量传递给一次 chmod 调用,而不是为每个文件单独启动一个进程;对于包含数千个条目的目录树,这一点很重要。脚本在单独的一行中恢复执行位,这是有意为之的。
在离开前检查结果
在执行任何递归变更后,使用以下两个命令可以找出重要错误。第一个命令列出您已无法进入的目录。
find . -type d ! -perm -u+x第二个命令列出计算机上的任何账户都可以编辑的文件。
find . -type f -perm -o+w在 -perm -o+w 中,开头的 - 表示“至少包含这些位”,因此无论设置了其他哪些权限,只要文件的 o 类包含写入位,就会匹配。两个命令都不输出任何内容,才是预期结果。对刚使用 -R 修改过的目录树,应从其顶层运行这两个命令;对您确实打算修改的少量路径运行 stat -c '%a %A %n',这样您认为设置的权限模式才会与内核实际保存的模式一致。
FAQ
chmod 755 和 chmod u+x 有什么区别?
数字模式是绝对模式。它会写入文件的所有权限位,因此您没有特别考虑的权限类别也会被重写。符号模式是编辑操作。u+x 为所有者添加一个权限位,同时保留其他权限位不变。当您明确知道文件所需的最终权限状态时,使用数字模式。需要添加或删除某一项权限时,使用符号模式。在执行任一命令前后运行 stat -c '%a %A %n' <file>,差异会显示在输出中。
为什么对包含 Web 文件的目录运行 chmod -R 755 是错误做法?
因为 -R 会对目录和普通文件设置相同的绝对模式,而两者需要不同的权限位。目录需要执行位才能遍历。HTML 和 CSS 等数据文件不需要执行位,将它们标记为可执行会导致 git 在下一次提交中记录文件模式变化,并由 rsync 将该变化复制到下一台机器。使用 chmod -R u=rwX,go=rX <dir>,让执行位只应用于目录;或者通过 find 配合 -type d 和 -type f,分别为不同类型设置各自的模式。
目录上的执行位有什么作用?
它授予遍历权限,而不是执行权限。目录中没有可运行的内容。执行位表示可以解析目录中的名称,因此要打开路径末尾的文件,路径中的每一级目录都必须具有执行位。读位是独立的,它授予列出名称的权限。两者可以单独存在:只有执行位时,您可以打开已知名称的文件,但 shell 无法在该目录中展开 glob;只有读位时,您可以看到文件名,但尝试打开其中任何文件都会失败。
什么时候应该使用大写 X,而不是小写 x?
只要命令具有递归性,就使用 X。X 会为目录设置执行位;对于普通文件,只有在已有某个执行位时才设置执行位。这样一次操作即可使目录树可遍历,同时不会将数据文件变成可执行程序。针对单个文件并且确实要修改该文件时,使用小写 x。需要记住一个限制:如果文件上的所有执行位都已被清除,X 无法恢复执行位,因为它已经没有可匹配的执行位。请使用 chmod u+x <file> 重新添加该权限。
chmod 会更改文件的所有者吗?
不会。chmod 只会更改权限位。所有权由 chown 指定用户,由 chgrp 指定组;将文件交给其他用户需要 root 权限。这一点很重要,因为权限位是根据文件中记录的所有者和组进行解释的,而不是根据最后写入文件的人进行解释。运行 stat -c '%U %G %a %n' <file>,可同时查看所有者、组和模式,然后再决定实际需要使用这三个命令中的哪一个。