跳转至

用户与用户组、文件权限、文件系统层次结构

本文已完稿并通过审阅,是正式版本。

导言

很多 Linux 的初学者都会对以下这些问题感到迷惑:

  • 为什么我改个设置,装个软件,动不动就要我输密码?
  • 为什么有些命令要 sudo,有些不要?
  • 为什么我运行不了网上下载的程序?为什么系统里面有些目录我看不了?
  • 我的 C 盘、D 盘跑哪里去了?根目录 (/) 里面一堆 etc, usr 是什么玩意啊?

以下内容将会解答你的疑问。

用户与用户组

为何需要「用户」

早期的操作系统没有用户的概念(如 MS-DOS),或者有「用户」的概念,但是几乎不区分用户的权限(如 Windows 9x)。而现在,这不管对于服务器,还是个人用户来说,都是无法接受的。

在服务器环境中,「用户」的概念是明确的:服务器的管理员可以为不同的使用者创建用户,分配不同的权限,保障系统的正常运行;也可以为网络服务创建用户(此时,用户就不再是一个有血有肉的人),通过权限限制,以减小服务被攻击时对系统安全的破坏。

而对于个人用户来说,他们的设备不会有第二个人在使用。此时,现代操作系统一般区分使用者的用户与「系统用户」,并且划分权限,以尽可能保证系统的完整性不会因为用户的误操作或恶意程序而遭到破坏。

Linux 下的用户简介

你可以查看 /etc/passwd 文件,来得到系统中用户的配置信息。

/etc/passwd 示例

以下是一个例子:

root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
(中间内容省略)
sshd:x:110:65534::/run/sshd:/usr/sbin/nologin
ustc:x:1000:1000:ustc:/home/ustc:/bin/bash
lxd:x:998:100::/var/snap/lxd/common/lxd:/bin/false
mysql:x:111:116:MySQL Server,,,:/nonexistent:/bin/false

在此文件中,每一行都代表一个用户,每行中用户信息由冒号 : 隔开,存储着包括用户名、用户编号 (UID, User ID)、家目录位置等信息。更多介绍,可以通过 man 5 passwd 查阅。

可以关注到,除了你自己以外,还有一个特殊的用户:root,和一大堆你素未相识的名字。下面将会进行介绍。

小知识:/etc/passwd 作用的变化

在 Unix 最初的时候,passwd 文件存储了用户密码的哈希1。但是,这个文件是所有用户都可以读取的。为了不让用户的密码哈希被任意获取,进而导致用户的密码被暴力破解,现在一般把密码存在别的地方。对于 Linux 来说,密码哈希信息存储在 /etc/shadow 里面,只有下文提到的根用户可以访问与修改。

根用户

在前文中我们知道,在使用 apt 安装软件时,我们需要在前面加上 sudo。这可以使我们以根用户 (root) 的身份安装软件。

根用户 / root 用户在 Linux 操作系统中拥有最高的权限,可以对系统做任何操作(包括删除所有系统文件这一类极端危险的操作)。root 用户的用户数据存储在 /root 下。

在我们使用 sudo 的时候,输入自己的密码并验证正确之后,sudo 就会以 root 用户的身份,执行后面我们希望执行的命令。而使用 apt 安装的软件存储在了系统的目录下,所以必须要以 root 用户的身份安装。这就是我们平时需要 sudo 来安装软件的原因。

谨慎使用 root 用户权限执行命令!

我们知道,root 用户可以对系统做极其危险的操作。当使用 root 权限执行命令时(如使用 sudo),一定要小心、谨慎,理解命令的含义之后再按下回车请不要复制网络上所谓的「Linux 优化命令」等,以 root 权限执行,否则可能会带来灾难性的后果

以下是一些会对系统带来毁灭性破坏的例子。 再重复一遍,不要执行下面的命令!

  • rm -rf /(删除系统中的所有可以删除的文件,包括被挂载的其他分区即使不以 root 权限执行,也可以删掉自己的所有文件。
  • mkfs.ext4 /dev/sda(将系统的第一块硬盘直接格式化为 ext4 文件系统。这会破坏其上所有的文件。)
  • dd if=/dev/urandom of=/dev/sda(对系统的第一块硬盘直接写入伪随机数。这会破坏其上所有的文件,并且找回文件的可能性降低。)
  • :(){ :|: & };:(被称为「Fork 炸弹」,会消耗系统所有的资源。在未对进程资源作限制的情况下,只能通过重启系统解决,所有未保存的数据会丢失。)

系统用户

除了你、root 和其他在用你的电脑/服务器的人(如果有)以外,剩下还有很多用户,如 nobody, www-data 等。它们由系统或相关程序创建,用于执行服务等系统任务。不要随意删除这些用户,以免系统运行出现问题。

技术层面上3,系统用户和普通用户的区别

一般地,在 Linux 中,系统用户的 UID 有一个指定范围,而这段范围在各个发行版中可能不同。如 Debian 使用了 100-999, 60000-64999 等区间分配给系统用户2

此外,由于系统用户的特殊性,它们一般默认禁止使用密码登录。

普通用户

普通用户可以登录系统,并对自己的家目录下的文件进行操作。所有普通用户的家目录都在 /home/ 下,位于 /home/username/ 的位置,其中 username 是用户名。

普通用户无法直接修改系统配置,也无法为系统环境安装或卸载软件。

切换用户:使用 susudo

sudo

sudo 命令可以让你以另一个用户的身份执行指定的命令。当然,它最常见的用途,就是能让普通用户以 root 的身份执行命令:不加入其他参数,sudo 后面直接加命令,我们在前面的课程中也见到很多次了。

root 用户执行上一条命令

你是否常常忘记敲 sudo,结果还要把后面的整条命令重新敲一遍?在发现权限不足之后有一个方便的「补救方案」:sudo !!,效果如下:

$ apt update
Reading package lists... Done
E: Could not open lock file /var/lib/apt/lists/lock - open (13: Permission denied)
E: Unable to lock directory /var/lib/apt/lists/
W: Problem unlinking the file /var/cache/apt/pkgcache.bin - RemoveCaches (13: Permission denied)
W: Problem unlinking the file /var/cache/apt/srcpkgcache.bin - RemoveCaches (13: Permission denied)
$ sudo !!
sudo apt update
[sudo] password for ustc:
Hit:1 http://mirrors.ustc.edu.cn/ubuntu bionic InRelease
(以下内容省略)

其实,在 Shell 中,!! 即代表上一条命令,可以和其他的命令结合使用。

那么,如何以 root 之外的用户的身份执行命令呢?加上 -u 用户名 的参数即可。

$ sudo -u nobody id
uid=65534(nobody) gid=65534(nogroup) groups=65534(nogroup)

这里,我们就用 nobody 这个用户的身份,执行了 id,得到了 nobody 的 UID 等信息。

修改 sudo 配置的例子:无密码执行 sudo (*)

sudo 的配置存储在 /etc/sudoers 文件中,仅 root 用户有权查看和修改。不要直接修改此文件。对这个文件的任何修改,都应该使用 visudo 这个命令去做。

默认的 Ubuntu 配置中,安装时创建的用户在 sudo 用户组(下文会提到这个概念)中。在 sudoers 文件中,它的配置像这样:

# Allow members of group sudo to execute any command
%sudo   ALL=(ALL:ALL) ALL

将配置行修改成以下即可。

%sudo   ALL=(ALL:ALL) NOPASSWD:ALL

sudoers 的配置比较复杂。可以使用 man sudoers,或在网络上搜索资料以得到更详细的说明。

su

su 命令用于直接切换用户,格式是 su 用户名。如果没有用户名这个参数,则切换到 root 用户。

在读完上面这句话之后,你可能会尝试切换到 root,但是却失败了:

$ su
Password:
(密码?什么密码?输我自己的密码试试?)
su: Authentication failure
$

这是因为,如 Ubuntu 等 Linux 发行版默认禁止了 root 用户的密码登录,只允许通过 sudo 提高权限。但是,我们可以用 sudo 运行 su,来得到一个为 root 用户权限的 shell。

$ sudo su
Password:
(没错,是我自己的密码)
# id
uid=0(root) gid=0(root) groups=0(root)
# exit
$

sudo su, sudo su - 等命令有一些细微的区别,可以阅读本章的补充材料。另外,也可以使用 sudo -i(与 sudo su - 等价),获得一个 root 权限 shell。

用户组简介

用户组是用户的集合。通过用户组机制,可以为一批用户设置权限。可以使用 groups 命令,查看自己所属的用户组。

$ groups
ustc adm cdrom sudo dip plugdev lxd

可以看到,用户 ustc 从属于多个用户组,包括一个与其名字相同的用户组。一般在用户创建的时候,都会创建与它名字相同的用户组。

对于普通用户来说,用户组机制会在配置部分软件时使用到。如在使用第八章的 Docker 时,可以把自己加入 docker 用户组,从而不需要使用 root 权限,也可以访问它的接口。

同样,用户组和用户一样,也有编号:GID (Group ID)。

命令行的用户配置操作

修改密码:passwd

可以使用此命令修改用户密码,格式为 passwd 用户名。如果没有输入用户名,则修改自己的密码。

(*) 简单的用户配置:adduser

adduser 是 Debian 及其衍生发行版的专属命令

如果你正在使用其他发行版(如 Fedora, CentOS 等),或者有更多的需求,则需要使用 usermod, useradd, groupadd 等替代。具体信息详询各自的 man 文档。

adduser 是 Debian 及其衍生发行版中附带的一个方便的用户管理脚本。它可以用来向系统添加用户、添加组,以及将用户加入组。输入:

$ sudo adduser 用户名

即可添加此用户。而输入

$ sudo adduser --group 组名

即可添加此用户组。将用户加入指定用户组也非常简单:

$ sudo adduser 用户名 组名

添加用户至 sudo 用户组

在通过 adduser 创建了新的用户后,直接使用 sudoroot 身份运行程序可能会得到:

$ sudo apt update
[sudo] password for ustc:
ustc is not in the sudoers file.  This incident will be reported.

除了可以通过 visudo 命令编辑 sudoers 文件外,还可以直接通过将新的用户加入到 sudo 用户组,以能够使用 sudo 命令。

$ sudo adduser ustc sudo

再次切换到新的用户即可看到使用 sudo 的提示:

To run a command as administrator (user "root"), use "sudo <command>".
See "man sudo_root" for details.

文件权限

在 Linux 中,每个文件和目录都有自己的权限。可以使用 ls -l 查看当前目录中文件的详细信息。

$ ls -l
total 8
-rwxrw-r-- 1 ustc ustc   40 Feb  3 22:37 a_file
drwxrwxr-x 2 ustc ustc 4096 Feb  3 22:38 a_folder

第一列的字符串从左到右意义分别是:文件类型(一位)、文件所属用户的权限(三位)、文件所属用户组的权限(三位)、其他人的权限(三位)。对于每个权限,第一位 r 代表读取 (Read),第二位 w 代表写入 (Write),第三位 x 代表执行 (Execute),- 代表没有对应的权限。

第三、四列为文件所属用户和用户组。

例如,上面的文件 a_file 为普通文件 (-),所属用户权限为 rwx,所属用户组权限为 rw-,其他人的权限为 r--,文件所属用户和用户组均为 ustc

执行权限意味着什么?

读取和写入权限是很容易理解的。但是执行权限是什么意思?对于一个文件来说,拥有执行权限,它就可以被操作系统作为程序代码执行。如果某个程序文件没有执行权限,你仍然可以查看这个程序文件本身,修改它的内容,但是无法执行它。

而对于目录来说,拥有执行权限,你就可以访问这个目录下的文件的内容。以下是一个例子:

$ ls -l
total 8
-rwxrw-r-- 1 ustc ustc   40 Feb  3 22:37 a_file
drw-rw-r-- 2 ustc ustc 4096 Feb  3 22:38 a_folder
$ (与上面不同,我们去掉了 a_folder 的执行权限)
$ cd a_folder
-bash: cd: a_folder/: Permission denied
$ (失败了,这说明,如果没有执行权限,我们无法进入这个目录)
$ ls a_folder
ls: cannot access 'a_folder/test': Permission denied
test
$ (列出了这个目录中的文件列表,但是因为没有执行权限,我们没有办法访问到里面的文件 test)
$ cat a_folder/test
cat: a_folder/test: Permission denied
$ cp a_folder/test test
cp: cannot stat 'a_folder/test': Permission denied
$ mv a_folder/test a_folder/test2
mv: failed to access 'a_folder/test2': Permission denied
$ touch a_folder/test2
touch: cannot touch 'a_folder/test2': Permission denied
$ rm a_folder/test
rm: cannot remove 'a_folder/test': Permission denied
$ (可以看到,即使我们有写入权限,在此目录中进行添加、删除、重命名的操作仍然是不行的)

为了更好地理解目录权限的含义,可以把目录视为一个「文件」来看待,这个文件包含了目录中下一层的文件列表——「读取」对应读取文件列表的权限,「写入」对应修改文件列表(添加、删除、重命名文件)的权限,「执行」对应实际去访问列表中文件、以及使用 cd 切换当前目录到此目录的权限。

有关文件与目录权限的完整表格,可以查看 Arch Wiki 的 File permissions and attributes 一页。

可以使用 chmod (change file mode bits) 修改权限,chown (change file owner) 修改文件所有者。具体的使用方法,请查阅相关的文档,这里不再列出。

例子:如何执行从网络上下载的程序或脚本

有时候,我们会从网上下载一些二进制的程序,或者根据网络的教程编写脚本程序,但当你想执行的时候却发现:

$ ./program
-bash: ./program: Permission denied

大多数情况下,这说明这个文件缺少执行 (x) 权限。可以使用 chmod +x 命令添加执行权限。

$ chmod +x program
$ ./program
(可以执行了)

文件系统层次结构

相信到现在你应该已经发现了:Linux 下文件系统的结构和 Windows 的很不一样。在 Windows 中,分区以盘符的形式来标识(如「C 盘」、「D 盘」),各个分区的分界线是很明确的。在系统所在的分区(一般为 C 盘)中,存储着程序文件 (Program Files),系统运行需要的文件 (Windows),用户文件 (Users) 等。这种组织形式源于 DOS 和早期的 Windows,并一直传承下来。

而 UNIX 系列采用了一种不一样的思路组织文件:整个系统的文件都从 /(根目录)开始,像一棵树一样,类似于下图。

Unix 下的文件系统结构简图

其他的分区以挂载 (mount) 的形式「挂」在了这棵树上,如图中的 /mnt/windows_disk/

那么在根目录下的这些目录各自有什么含义呢?这就由文件系统层次结构标准 (FHS, Filesystem Hierarchy Standard) 来定义了。这个标准定义了 Linux 发行版的标准目录结构。大部分的 Linux 发行版遵循此标准,或由此标准做了细小的调整。以下进行一个简要的介绍。也可以在官网查看标准的具体内容。

当然,实际情况不一定会和以下介绍的内容完全一致。可以使用 man hierman file-hierarchy 查看你的系统中关于文件系统层次结构的文档。

/bin
存储必须的程序文件,对所有用户都可用。
/boot
存储在启动系统时需要的文件。
/dev

存储设备文件。

什么是设备文件?

在 Linux 的哲学中,存在着「一切皆文件」这样的思想。设备文件就是计算机设备抽象成文件的形式,程序和用户可以以读写普通文件的方式向这些文件输入内容,或者从文件中获取内容。系统驱动程序会相应处理用户对对应设备文件的输入和输出。

有一些常用的设备文件如:

  • /dev/null:总是返回空(EOF)数据。
  • /dev/zero:总是返回零数据。
  • /dev/urandom:输出随机数据。

配合第六章中提到的重定向功能,这些设备文件可以帮助我们做到丢弃程序输出等操作。

/etc

存储系统和程序的配置文件。

注册表与配置文件

Windows 系统与其上很多程序都使用注册表,而非文件的形式存储配置信息。注册表是一个数据库,拥有数据库的优点(如原子性),集中地管理配置项。而 Linux 下的程序更喜欢将配置以文件的形式存储,保持配置简单,便于用户编辑与备份。

当然,这不是绝对的。现在 Windows 下的 .NET 程序更偏向于将配置存储在 XML 文件中,而 Linux 下的 GNOME 桌面环境也采取了类似于注册表的形式: GConf,存储自己的配置信息。

/home

用户的家目录。存储用户自己的信息。

/lib

存放系统运行必须的程序库文件。

/media/mnt

这两个目录都用于挂载其他的文件系统。/media 用于可移除的文件系统(如光盘),而 /mnt 用于临时使用。

/opt

存放额外的程序包。一般将一些大型的、商业的应用程序放置在这个目录。

/root

root 用户的家目录。

/run

系统运行时的数据。在每次启动时,里面的数据都会被删除。

/sbin

存储用于系统管理,以及仅允许 root 用户使用的程序。如 fsck(文件系统修复程序)、reboot(重启系统)等。

/srv

存储网络服务的数据。

/tmp
临时目录,所有用户都可使用。
/usr

大多数软件都会安装在此处。其下有一些目录与 / 下的结构相似,如:

  • /usr/bin
  • /usr/lib
  • /usr/sbin

此外,还有一些目录:

  • /usr/include: 存储系统通用的 C 头文件。当然,里面会有你非常熟悉的头文件,如 stdio.h
  • /usr/local: 存储系统管理员自己安装的程序,这些文件不受系统的软件管理机制(如 apt)控制。/usr/local 里面的层次结构和 /usr 相似。
  • /usr/share: 存储程序的数据文件(如 man 文档、GUI 程序使用的图片等)。

usrmerge

近年来,部分发行版选择将 /usr/bin/usr/sbin/usr/lib 与根目录下的 /bin/sbin/lib 合并,根目录下的对应目录软链接到 /usr 下的目录,以简化文件结构。

Ubuntu 与 Debian 中可以安装 usrmerge 软件包来进行转换。

/var

存储会发生变化的程序相关文件。例如下面的目录:

  • /var/log:存储程序的日志文件。
  • /var/lib:存储程序自身的状态信息(如 lock file)。
  • /var/run:存储程序运行时的数据(部分发行版会将该目录符号链接到 /run 目录)。
  • /var/spool:存储「等待进一步处理」的程序数据。

思考题

nobody 用户

关于 nobody 用户,网络上有一种说法称,所有网络服务(如 Web 服务器)都应该以此用户身份运行。从安全性的角度反驳此观点。

系统用户的默认 Shell

/etc/passwd 中最后一列是用户的默认 Shell(如果你没有修改过的话,那么一般是 /bin/bash)。为什么会有很多用户的默认 Shell 是 /usr/sbin/nologin 或者 /bin/false?这样做有什么意义?

启用 root 用户

在 Ubuntu 等 Linux 发行版中,root 用户是默认禁用(无法直接使用密码登录)的。如何启用此用户?

文件的可执行权限

如果向一个 MP3 音频文件添加「可执行」(x) 属性,那么这个文件就可以被执行吗?为什么?

sudo cd?

当需要浏览仅 root 用户可查看的目录时,很多人的第一反应是 sudo cd xxx,但最终失败了。尝试解释这样做不可行的原因。

引用来源


  1. 这里的哈希,指经过了密码哈希函数 (Cryptographic hash function) 的处理。密码哈希函数是一种特殊的单向函数,将任意大小的数据映射到一串长度固定的字符串,并且拥有一些优良的性质(如难以找到两个不同的数据,使得映射后的字符串相同),使其破解难度加大。 

  2. https://www.debian.org/doc/debian-policy/ch-opersys.html#uid-and-gid-classes 

  3. 然而,对于 Linux 内核来说,系统用户和真实的用户其实没有区别,除了 UID = 0 的用户 (root) 以外。「系统用户」是一个约定俗成而产生的概念。