上一篇讲 cpmvrm 时,权限已经在旁边冒过好几次头:cp 覆盖一个已存在的文件,目标的属主和权限不会被源文件替换;mv 跨文件系统时这些属性可能根本没带过去;rm 删掉一个文件,靠的也不是文件自己的权限。这些现象背后是同一套机制。这篇就把它正面拆开:ls -l 里那九个字符到底在说什么,rwx 在文件和目录上为什么是两套含义,以及 chmod 的符号写法和数字写法如何一一对应。

先把 ls -l 的第一列读出来

1$ ls -l /srv/app/deploy.sh
2-rwxr-xr-- 1 deploy www-data 2140 Aug 12 09:30 deploy.sh

第一列十个字符。第 1 位是类型,常见的是 - 普通文件、d 目录、l 符号链接。后面九位是 三组各三位rwx,依次属于:

  • u,文件的属主(ls -l 第三个字段 deploy 就是属主);
  • g,属组里的其他人(第四个字段 www-data);
  • o,其余所有人。

所以权限回答的是两个问题:给谁(u / g / o),允许做什么(r / w / x)。

这里有个容易踩的误区:这三类不是叠加的。进程只按自己所属的那一个类别判定,顺序是属主、属组、其他。所以一个文件权限是 ---r--r-- 时,属主自己反而读不了它,尽管 o 那一组写着 r

在文件上,r、w、x 大致符合直觉,但有一处要小心

对普通文件:r 读内容,w 改内容,x 把文件当命令执行。

要小心的是 x 不包含 r。二进制程序通常只有 x 就能跑起来,因为内核自己去读这个文件,那不算你的读权限;可脚本不行——内核看到开头的 #!,会把文件交给解释器处理,解释器再 以你的身份 打开并读它。所以脚本必须同时有 rxchmod 111 script.sh 看起来“人人可执行”,实际运行只会得到 Permission denied。要跑脚本,用 755,或者只给自己用的 700

在目录上,rwx 换了一整套含义

这是本篇最需要记住的一节。

  • r:能读到目录里那张“名单”,也就是 ls 能列出名字。
  • w:能改动这张名单——在里面新建、删除、重命名条目。
  • x:能穿过它——cd 进去,或者按路径访问里面的东西。内核文档里管这个叫 search 权限。

三条推论很实用:

第一,删掉或改名一个文件,要的是它所在目录的 wx,跟文件自己的权限无关。 别人给你一个只读文件,不代表你删不掉它;反过来,你对那个目录没有 w,文件给你 rwx 也删不了。这就是上一篇说“rm 删的是名字不是内容”的另一面:既然删的是目录里的一条记录,要权限的自然就是目录。

第二,目录的 x 在路径上每一层都要有。 /home/deploy/app/config.yml,只要 /home/home/deploy 少了 x,哪怕 config.yml777 你也读不到它。这和第一篇讲路径解析的是同一条规则。

第三,光有 w 没有 xw 就落不了地。 在目录里创建一个条目或者删掉一个条目,都要求同时具备 w 和 search 权限。所以 -w- 这种权限配出来是空转的。反过来,只有 r 没有 xr--)时,能列出名字,却看不到每个文件的详细信息(ls -l 里那些字段会是问号),也进不去目录。

绘制中

顺带解释两个常见的权限值:/tmp1777,就是“谁都能在里面创建、也能删自己建的东西”,再靠那个 1(sticky)挡住别人删你的文件;/home/you 通常是 700,别人连名字都列不出来。

新文件刚创建时的权限不是随意的:文件基准是 666、目录基准是 777,再减去你的 umask(通常是 022),所以新建文件常常是 644、新建目录是 755。不加 -pcp 复制出来的文件,权限也是按你自己这套默认规则生成的,这也是上一篇那个现象的原因。

chmod 符号写法:描述“要改动什么”

1chmod u+x deploy.sh      # 属主加执行
2chmod g-w,o-r file       # 属组去掉写,其他人去掉读
3chmod a=r file           # 三类都只留读

格式是 [ugoa...][+-=][rwx...],多段用逗号连起来。+ 在现有位上增加,- 移除,= 是“重设成这些”——没提到的位会被清掉,所以 chmod g=r 会顺手把 g 的 wx 一起摘掉。

两个细节值得单独记:

省略 who 时(比如 chmod +x),GNU 的 chmod 把它当作 a,但 你的 umask 里已经置上的那些位不受影响。日常 umask 是 022,里面没有 x 位,所以 chmod +x script.sh 实际上是给属主、属组、其他人全都加了执行权——这比很多人以为的要宽松。

权限字母里还能写大写的 X:只对目录、或者本来就已经对某类用户可执行的文件加 x。所以“把一整棵目录树开放给同组”的标准写法是 chmod -R go+rX dir,它不会把普通的文本文件也标成可执行。

chmod 数字写法:描述“最终要什么”

每一位是 r=4w=2x=1 相加,三位依次给 ugo

数字位模式含义数字位模式含义
0---无权限4r--只读
1--x只执行5r-x读 + 执行
2-w-只写6rw-读 + 写
3-wx写 + 执行7rwx全权限

于是两种写法一一对应:

  • chmod 644 file = chmod u=rw,g=r,o=r file(常见的配置、代码、网页文件)
  • chmod 755 dir = chmod u=rwx,g=rx,o=rx dir(目录、可执行文件)
  • chmod 600 key.pem = chmod u=rw,g=,o= key.pem(私钥、.env 这类凭据文件)
  • chmod 2775 shared = 在 775 之上给目录加 setgid,让它里面新建的文件自动归到目录的属组

关键区别在于:数字写法是绝对赋值,相当于对三类用户都用 =,它完全不看原来的权限是什么。符号写法既可以做加法减法(+ / -),也可以做绝对赋值(=)。想改一两个位、又不想影响别处时,用符号写法更安全。

数字写法还能多加一位放在最前面,控制三个“特殊位”:4 是 setuid,2 是 setgid,1 是 sticky。省略的位按前导零理解,所以 chmod 755 就是 chmod 0755,不会动特殊位。

这里有一个真实的例外要记住:对目录,GNU chmod 用数字写法时会保留目录上已有的 setuid、setgid 位,除非你明确写出来。前面那个 chmod 2775 设好 setgid 的共享目录,之后有人为了改权限跑一次 chmod 755,setgid 不会掉;按文档说明,想用数字写法清掉它需要额外多写一位前导零(如 00755),用符号写法 g-s 则可以直接清掉。

ls -l 里这些特殊位会挤进 x 的位置显示:s(或 S)出现在 u 位是 setuid,出现在 g 位是 setgid,t(或 T)出现在 o 位是 sticky。大写和小写的区别是那一格有没有 x:有 x 是小写,没有是大写。

几个常见场景和边界

场景常见写法
配置、代码、静态文件644
目录755
要执行的脚本、二进制755,只自己用可以 700
私钥、凭据文件600~/.ssh 目录 700,里面的 id_*600
组内协作目录2775(带 setgid),目录内的文件用 664

最后两条边界:root(更准确地说,具备 CAP_DAC_OVERRIDE 的进程)基本绕过这套检查,所以“我这条命令能跑通”不等于“权限配对了”,验证时最好换普通用户身份再试一次。另外,ls -l 里类型位是 l 的符号链接,它自己的权限位在 Linux 上不生效,实际判定的是它指向的那个文件。

动手核对一下
  1. deploy.sh 设成“属主可读可写可执行,属组和其他人只能读”,符号写法和数字写法各写一条。
  2. 目录 shared 现在是 755,属组 members 里的人需要能在里面新建、删除文件,也能读到里面的文件。目录该怎么设?里面已有的文件还需要处理吗?
  3. 一个文件权限是 -rwxrwxrwxcat 它却报 Permission denied,最有可能是什么原因?

参考答案:1. chmod u=rwx,g=r,o=r deploy.shchmod 744 deploy.sh。2. 目录设 775(想让它内部新建的文件自动继承属组就设 2775);目录权限只影响“能不能在名单上动名字”和“能不能穿过”,已有文件本身的内容权限是另一回事,775 不会顺带放宽它们。3. 路径上某一层目录缺少 x,比如 shared 被设成了 644