Linux 学习笔记(四)- 文件系统
说实话我看了一下文件系统架构的介绍,不是很明白,至少按照《Linux 从入门到精通》的顺序读到这里的时候,我没看明白,只知道 Linux 里几乎所有操作最终都可以理解为对文件的修改。所以我就不在这里写太多的学习笔记了,借用作者书中的一段话:
要理解Linux的文件系统架构,看来的确需要耗费一定的脑力。 如果经过努力仍然不明白上面这些文字在说些什么,一个好的建议是:不要管那么多,先使用。没有人会为了上网而首先去学习路由器原理,但一个接触了几年网络的人总能对路由器是什么这个问题说上几句。所以无论如何,首先去实践。
So…… 管他呢,先实践起来~
Linux 系统主要目录及其内容
目录 | 内容 |
---|---|
/bin | 构建最小系统所需要的命令(最常用的命令) |
/boot | 内核与启动文件 |
/dev | 各种设备文件 |
/etc | 系统软件的启动和配置文件 |
/home | 用户的主目录 |
/lib | C 编译器的库 |
/media | 可移动介质的安装点 |
/opt | 可选的应用软件包(很少使用) |
/proc | 进程的映像 |
/root | 超级用户 root 的主目录 |
/sbin | 和系统操作有关的命令 |
/tmp | 临时文件存放点 |
/usr | 非系统的程序和命令 |
/var | 系统专用的数据和配置文件 |
快速上手:搞一个小团体
共享文件对一个团队而言非常重要。团队的成员常常需要在一台服务器上共同完成一项任务。那我们来创建一个“小团体”吧,看看如何实现成员间的文件共享,假设团队成员如下:
用户组:fancy
工作目录:/home/blog/
组长:yylittlecat
成员:tommy、archie
编外人员:apple
- 创建用户组和用户成员:
1 | # 新建一个名为 fancy 的用户组 |
- 新建博客的工作目录:
1 | $ cd /home |
- 现在,任何人都可以访问这个新建的目录,而只有 root 用户才拥有该目录的写权限,我们希望让 fancy 组的成员拥有这个目录的读写权限,并禁止其他无关的用户查看这个目录:
1 | $ sudo chgrp fancy blog/ # 将 blog 目录的所有权交给 fancy 组 |
这个时候我们 /home 目录下的文件列表和他们的权限是这样的,为了方便查看,我们把主机和目录信息贴上:
1 | [yylittlecat@VM-0-13-centos home]$ ls -l |
此刻 /home/blog 目录的权限,对于拥有者 yylittlecat 和属于 fancy 用户组的成员都具备可读可写可执行的权限,但我们计划了一位“编外人员” apple 对吧,我们切换到 apple ,会发现他没有权限访问该目录:
1 | [apple@VM-0-13-centos home]$ cd blog |
截止到这里,建立小团体体验完毕了。/home/blog/ 存放的是我们的博客内容,这个时候会发现访问博客 403 了,哈哈哈…403 状态码意为服务器成功解析了请求但是客户端没有访问该资源的权限。那这又是怎么回事儿呢?
文件和目录的权限
上面我们通过 ll
或者 ls -l
命令查看到的文件属性,(选几行为例)从左至右依次是:
1 | 文件的权限标志 | 文件的链接个数 | 文件所有者的用户名 | 该用户所在的用户组 | 文件的大小 | 最后一次被修改的日期 | 目录/文件名 |
我们注意到,权限标志貌似是一些特殊的符号:d、r、w、x 、- 等等,通过 chmod 命令修改的也是这些内容,这几个符号到底代表什么呢?让我们一探究竟:
权限
Linux 为 3 种人准备了权限——文件所有者 (属主)、文件属组用户和其他人。因为有了“其他人”,这样的分类将世界上所有的人都包含进来了。但 root 用户其实是不应该被算在“其他人”里面的。root 用户可以查看、修改、删除所有人的文件——即不要忘了 root 拥有控制一台计算机的完整权限。
文件所有者通常是文件的创建者,但这也不是一定的。可以中途改变一个文件的属主用户,这必须直接由 root 用户来实施。这句话换一种说法或许更贴切:文件的创建者自动成为文件所有者(属主),文件的所有权可以转让,转让“手续”必须使用 root 权限办理。
可以(也必须)把文件交给一个组,这个组就是文件的属组。文件属组中的用户按照设置对该文件享有特定的权限。通常来说,当某个用户(如 tommy)建立一个文件时,这个文件的属主就是 tommy,文件的属组是只包含一个用户 tommy 的 tommy 组。当然,也可以设置文件的属组是一个不包括文件所有者的组,在文件所有者执行文件操作时,系统只关心属主权限,而组权限对属主是没有影响的。
最后,“其他人” 就是不包括前两类人和root用户在内的“其他”用户。通常来说,“其他人”总是享有最低的权限(或者干脆没有权限)。
那都需要设置哪些权限呢?
可以赋予某类用户对文件和目录享有 3 种权限:可读(read)权限,可写(write)权限,可执行(execute)权限。对于文件而言,拥有读取权限意味着可以打开并查看文件的内容,写入位控制着对文件的修改权限。而是否能够删除和重命名一个文件则是由其父目录的权限设置所控制的。
要让一个文件可执行,必须设置其执行权限。可执行文件有两类,一类是可以直接由 CPU 执行的二进制代码;另一类是 Shell 脚本程序,例如我们常用的命令。
对目录而言,所谓的可执行权限实际控制了用户能否进入该目录;而可读权限则负责确定能否列出该目录中的内容;可写权限控制着在目录中创建、删除和重命名文件。因此目录的可执行权限是其最基本的权限,换句话说是想访问一个目录以及里面的文件,至少要给到可读可执行两个权限,只有可读,我们还是访问不了的。
FYI:只有文件的所有者和 root 用户才有权修改文件的权限。
drwxrwxrwx 这个权限标志一共10位,第 1 位 代表文件类型,常用有两个数值:d
和 -
,d
代表目录,-
代表非目录,当然还有其他的(、b、c)。后面 9 位可以拆分为 3 组来看,分别对应不同用户:2-4 位代表所有者 user 的权限说明,5-7 位代表组群 group 的权限说明,8-10 位代表其他人 other 的权限说明。r
代表可读(read)权限,w
代表可写(write)权限,x
代表可执行(execute)权限,后 9 位中 -
表示不具备对应的权限。drwxrwxrwx
表示所有用户都对这个目录有可读可写可执行权限。
Linux 中的文件类型:
文件类型 | 符号 | 文件类型 | 符号 |
---|---|---|---|
普通文件 | - | 本地域套接口 | s |
目录 | d | 有名管道 | p |
符号设备文件 | c | 符号链接 | l |
块设备文件 | b |
chmod
说回修改权限的命令,不好记的话,笔者觉得记英文缩写是个不错的选择:
chmod - change file mode bits
使用字母修改权限:
u 代表所有者(user)
g 代表所有者所在的组群(group)
o 代表其他人,但不是 u 和 g (other)
a 代表全部的人,也就是包括 u、g 和 o
所以 chmod 参数的简单拼接方式就是,修改谁的权限就使用对应位置的字母,授予权限就是 +,删除权限就是 - ,直接设置权限用 =,后面跟上想修改的权限、要操作的目录。
举例 - 给其他人(other)授予写 /home/apple/test 这个文件的权限:
1 | $ sudo chmod o+w /home/apple/test |
举例 - 删除组群(group)和其他人(other)对 /home/apple/test 这个文件的读和写的权限:
1 | $ sudo chmod go-rw /home/apple/test |
举例 - 给所有者(user)、用户组(group)设置读写权限,其他人(other)只读:
1 | $ sudo chmod ug=rw,o=r /home/apple/test |
使用数字修改权限:
r、w、x 有对应的数字:r - 4,w - 2,x - 1。那这些数字是怎么来的呢?rwx 在计算中实际上占用了 3 位,对于一个权限只有 1 和 0 ,也就是有没有的区别,对应到二进制上,3 位二进制数对应 1 位八进制数,因此就可以用一个八进制数字表示一组权限。前面说了权限标志十位中的后九位分为三组,用数字修改权限时也是对应三组,每三个一组把想修改的权限数字加起来,得到三位数的一个数字。
举例 - 针对 /home/blog/www 目录,给所有人授予可读可写可执行的权限:
1 | $ sudo chmod -R 777 /home/blog/www |
按照规则:user 的三个加起来 4+2+1=7,group 的三个加起来 4+2+1=7,other 的三个加起来 4+2+1=7,=>777。对应的:5=4+1,表示拥有可读可执行权限,但是没有写权限;0 代表没有任何权限。想记的话,也可以算几个常用的:-rw-------
(600) 只有所有者才有读和写的权限-rw-r--r--
(644) 只有所有者才有读和写的权限,组群和其他人只有读的权限-rwx------
(700) 只有所有者才有读,写,执行的权限-rwxr-xr-x
(755) 只有所有者才有读,写,执行的权限,组群和其他人只有读和执行的权限-rwx–-x--x
(711) 只有所有者才有读,写,执行的权限,组群和其他人只有执行的权限-rw-rw-rw-
(666) 每个人都有读写的权限
八进制 | 二进制 | 权限 | 八进制 | 二进制 | 权限 |
---|---|---|---|---|---|
0 | 000 | --- |
4 | 100 | r-- |
1 | 001 | --x |
5 | 101 | r-x |
2 | 010 | -w- |
6 | 110 | rw- |
3 | 011 | -wx |
7 | 111 | rwx |
这个好像也不用死记硬背,会 8 以内的加减法是不是就可以了~😂
chown
chown - change file owner and group
用于设置文件所有者和文件关联的组,需要 root 权限。
chgrp
chgrp - change group ownership
用于变更文件或目录的所属群组,与 chown 命令不同,chgrp 允许普通用户改变文件所属的组,只要该用户是该组的一员。
更多的使用方式在实践中进行吧~~
-R
-R Change the user ID and/or the group ID for the file hierarchies rooted in the files instead of just the files themselves.
这三个命令常用的一个选项是 -R
,注意这里是大写。加上这个选项会循环把该目录下的子目录、子文件的权限、所有者等等都一起进行修改,不然改的只是父目录,等我们需要访问子目录的时候就发现有问题啦…
操作文件和目录
学会建立文件和目录,是 Linux 系统管理的第一步。虽然我没有先学这里,哈哈…
建立目录 - mkdir
mkdir - make directories
可以建立一个或多个目录。
-m, –mode=MODE
set file mode (as in chmod), not a=rwx - umask-p, –parents
no error if existing, make parent directories as needed
举例 - 在用户主目录下新建两个文件夹 document、picture:
1 | $ cd ~ # 进入用户主目录 |
举例 - 用户也可以使用绝对路径来新建目录:
1 | $ mkdir ~/picture/temp # 在主目录下新建名为temp的目录 |
由于主目录下 picture 已经存在,因此这条命令是合法的。但是当用户试图运行下面这条命令时,mkdir 将提示错误:
1 | $ mkdir ~/test/temp |
这是因为当前在用户主目录下并没有 test 这个目录,自然也无法在 test 下创建 temp 目录了。为此 mkdir 提供了 -p
选项,用于完整地创建一个子目录结构:
1 | $ mkdir -p ~/test/temp |
在这个例子中,mkdir 会首先创建 test 目录,然后创建 temp,在需要创建一个完整目录结构的时候,这个选项是非常有用的。
另外一个常用的选项是 -m
,在新建目录的同时指定对应的权限:mkdir -p -m 700 ~/test/tempx
,到这里不禁要问了,不特意指定的话默认权限是什么样的呢?这就涉及到另一个命令:umask。
权限掩码 - umask
umask (1) - bash built-in commands, see bash(1)?
指定在建立文件时预设的权限掩码。
umask 可用来设定[权限掩码]。[权限掩码]是由3个八进制的数字所组成,将”满级(a=rwx)(777)”的权限减掉权限掩码后,即可产生建立文件时预设的权限。使用 umask
查看8进制数字或者 umask -S
查看以符号形式表示的权限掩码,如果后面跟上参数,那意味着重新指定权限掩码或设定默认权限:
1 | $ umask |
所以我用的这台虚拟机上,目录的默认权限就是 drwxr-xr-x
,也就是 777-022=755
。
1 | [yylittlecat@VM-0-13-centos test]$ ll |
建立空文件 - touch
touch – change file access and modification times
设置文件的访问和修改时间,如果文件不存在的话,会使用默认权限创建一个空文件。
举例 - 在当前目录下新建一个名为 test 的文件。
1 | $ touch test |
用 touch 命令建立的文件是空文件(也就是不包含任何内容的文件)。空文件对建立某些特定的实验环境是有用。另外,当某些应用程序因为缺少文件而无法启动,而这个文件实际上并不那么重要时,可以建立一个空文件暂时“骗过”这个程序。
touch 命令的另一个用途是更新一个文件的建立日期和时间。例如,对于 hello 这个文件,使用 ls-l
命令显示这个文件的建立时间为2022年2月18日的10点30分,并且文件是有内容的。
1 | $ ls -l |
使用 touch 命令更新后,建立时间变成了2022年3月15日的15点18分,但内容并没有变化。
1 | $ touch hello |
touch 命令的这个功能在自动备份和整理文件时非常有用,这使得程序可以决定哪些文件已经被备份或整理过了。
移动和重命名 - mv
mv - move (rename) files
对文件或目录进行移动、重命名。
测试目录:用户主目录 ~ 和主目录下的 test/ 目录。
可以移动文件或者目录:
1 | $ mv hello test/ |
mv 命令在执行过程中不会显示任何提示信息,那如果目录下有一个同名文件会怎么样呢?做一个小测试:
(1) 首先在主目录下新建一个名为 hello 的文件:
1 | $ cd ~ |
(2) 在主目录下新建一个 test 目录,然后在 test/ 下同样建立一个名为 hello 的文件:
1 | $ mkdir test |
(3) 我们在两个 hello 文件的内容上做了区分,现在把主目录下的 hello 移动到 test/ 目录下,并且查看 test/hello 的内容,会发现文件中原本的内容已经被覆盖了,也就是 mv 把 test/ 目录下的同名文件替换了,但却没有给出任何警告:
1 | $ cd .. |
这样有没有问题呢?可能有。用户可能不经意间就把一个重要文件给删除了。为此 mv 提供了一个 -i
选项用于发现这样的情况:
1 | $ mv -i hello test/ |
回答 y 表示覆盖,回答 n 表示跳过这个文件(保持原样不动)。
另一个比较有用的选项是 -b
。这个选项用一种不同的方式来处理刚才这个问题。在移动文件前,首先在目标目录的同名文件的文件名后加一个“~”号,从而避免这个文件被覆盖。例如:
1 | $ mv -b hello test/ |
Linux 没有“重命名”这个命令,原因很简单,即没有这个必要。重命名无非就是将一个文件在同一个目录里移动,这是 mv 最擅长的工作。
1 | $ mv hello~ hello_bak |
因此对 mv 比较准确的描述是,mv 可以在移动文件和目录的同时对其重命名。
诶,那如果 test/ 目录下已经存在一个 hello~
,重复执行 mv -b hello test/
会是什么效果呢?又或者同时使用这两个选项 mv -i -b hello test/
会发生什么呢?我有够无聊,是不是~哈哈😂
复制文件和目录 - cp
cp – copy files
复制文件和目录。
cp命令用来复制文件和目录。下面这条命令将文件 hello.log 复制到 test 目录下。
1 | $ cp test.php test/ |
和 mv命令一样,cp 默认情况下会覆盖目标目录中的同名文件。可以使用 -i 选项对这种情况进行提示,也可以使用 -b 选项对同名文件改名后再复制。这两个选项的使用和 mv 命令中一样。
1 | $ cp -i hello.log test/ |
回答 y 表示覆盖,回答 n 表示跳过这个文件。
1 | $ cp -b hello.log test/ |
cp命令在执行复制任务的时候会自动跳过目录。例如:
1 | $ cp test/ document/ |
为此,可以使用 -r 选项,这个选项将子目录连同其中的文件一起复制到另一个子目录下。
1 | $ cp -r test/ document/ |
删除目录和文件 - rmdir、rm
rmdir – remove directories
删除目录。
rm, unlink – remove files or directories
删除文件、目录。
rmdir 命令用于删除目录。使用起来很简单,只需要在后面跟上要删除的目录作为参数即可,也可以同时删除多个目录:
1 | $ rmdir temp |
但是 rmdir 只能删除空目录,删除非空目录会提示错误:
1 | $ rmdir test |
因此,在使用 rmdir 删除一个目录之前,首先要将这个目录下的文件和子目录删除。删除文件需要用到 rm 命令了,当然 rm 也可以用来删除目录,而且比 rmdir 更为“高效”。
1 | $ ls test/ |
和 mv 等命令一样,rm 不会对此作任何提示。通过 rm 命令删除的文件将永远地从系统中消失了,而不会被放入一个称作“回收站”的临时目录下(尽管某些恢复软件可能找回一些文件,但只是“可能”而已)。一个比较安全的使用 rm 命令的方式是使用 -i 选项,这个选项会在删除文件前给出提示,并等待用户确认。
1 | $ rm -i test/hello |
回答 y 表示确认删除,回答 n 表示跳过这个文件。对于只读文件,即便不加上 -i 选项,rm 命令也会对此进行提示。
1 | $ rm hello_bak |
可以使用 -f 选项来避免这样的交互式操作。rm 会自动对这些问题回答 y。
1 | $ rm -f hello_bak |
带有 -r 参数的 rm 命令会递归地删除目录下所有的文件和子目录。例如,下面这个命令会删除 Photos 目录下所有的目录、子目录及子目录下的文件和子目…..最后删除 Photos目录。也就是说,把 Photos 目录完整地从磁盘上移除了(当然前提是拥有这样操作的权限)。
1 | $ rm -r Photos/ |
使用 rm 命令的时候应该格外小心,特别是以 root 身份执行该命令时。无论熟不熟悉 Linux,可能大家都看过下面这个动图,哈哈,说的就是这个意思:
前面的学习中提到过,/
表示 Linux 的根目录,~
表示用户的主目录,图中的命令是把根目录的一切都删除掉了。我怀疑是不是有人习惯性的把 /
理解为当前所工作的目录了,因为我有两次手贱也在想要打开的子目录前面输入 /
,提示我目录不存在,才反应过来输错了😂……
所以在删除一个文件/目录前,一定要认真评估后果。如果要使用 -f 和 -r 选项,要确定这是必须的。
输入输出重定向和管道
输出重定向 - >、>>
程序在默认情况下输出结果的地方被称为标准输出(stdout)。通常来说,标准输出总是指向显示器。例如,下面的 ls 命令获取当前目录下的文件列表,并将其输出到标准输出,于是用户在屏幕上看到了这些文件名:
1 | $ cd ~ |
输出重定向用于把程序的输出转移到另一个地方去。下面这条命令将 ls 的输出重定向到 ls_out 文件中:
1 | $ ls ~ > test/ls_out |
这样,ls 的输出就不会在显示器上显示出来,而是出现在 test 目录下的 ls_out 文件中,每行显示一个文件名:
1 | $ cat test/ls_out |
如果 ls_out 文件不存在,那么输出重定向符号 >
会试图建立这个文件。如果 ls_out 文件已经存在了,那么 >
会删除文件中原有的内容,然后用新内容替代:
1 | $ uname -r > test/ls_out |
可以看到,>
并不会礼貌地在原来那堆文件名的后面添上版本信息,而是直接覆盖了刚刚 ls 命令的输出。如果要保留原来文件中的内容,应该使用输出重定向符号 >>
:
1 | $ date > test/date_out # 将 date 命令的输出重定向到 date_out 文件 |
输入重定向 - <、<<
和标准输出类似,程序默认情况下接收输入的地方被称为标准输入(stdin)。通常来说,标准输入总是指向键盘。例如,如果使用不带任何参数的 cat 命令,那么 cat 会停在那里,等待从标准输入(也就是键盘)获取数据。书上说使用 <
重定向符号可以让程序从一个文本中获取输入……所以下面的两个命令是一回事儿吧:
1 | $ cd ~ |
另一种输入重定向的例子被称为立即文档(here document)。这种重定向方式使用操作符 <<
。立即文档明确告诉 Shell 从键盘接受输入,并传递给程序。现在看下面这个例子:
1 | $ cat<< EOF |
cat 命令从键盘接受两行输入,并将其送往标准输出。和本节开头的例子不同的是,给立即文档指定了一个代表输入结束的分隔符(在这里是单词 EOF),当 Shell 遇到这个单词的时候,即认为输入结束,并把刚才的键盘输入一起传递给命令。所以这次 cat 命令会将用户的输入一块显示,而不是每收到一行就迫不及待地把它打印出来。
用户可以选择任意一个单词作为立即文档的分隔符,像 EOF、END、eof 等都是不错的选择,只要可以确保它不是正文的一部分即可。
那么,是否可以让输入重定向和输出重定向结合在一起使用?答案是肯定的。
1 | $ cat << END >> test/date_out |
这条命令首先让 cat 命令以立即文档的方式获取输入,然后再把 cat 的输出重定向到 date_out 文件。查看 date_out 文件,应该可以看到拼接的这些内容:
1 | $ cat test/date_out |
管道 - |
管道将“重定向”再向前推进了一步。通过一根竖线 |
,将一条命令的输出连接到另一条命令输入。下面这条命令显示了如何在文件列表中查找文件名中包含某个特定字符串的文件:
1 | $ cd test/ |
ls 首先列出当前目录下的所有文件名,管道 |
接收到这些输出,并把它们发送给 grep 命令作为其输入,最后 grep 在这堆文件列表中查找包含字符串 out 的文件名,并在标准输出(也就是显示器)显示。
可以在以行命令中使用多个管道,将多个命令结合起来,写出复杂的 Shell 脚本。看书看到这里,我仿佛已经会写脚本了一样,哈哈哈~🤓
Linux 的文件系统,暂时学习到这里。