Skip to content

文件和目录

文件和目录管理

几乎所有的计算机操作系统都使用目录结构组织文件。何为目录结构组织文件呢?
具体来说就是在一个目录中存放子目录和文件,而在子目录中又会进一步存放子目录和文件,以此类推形成了一个树形的文件结构,由于其结构很像一棵树的分支,所以该目录又被称为“目录树”。
在 Linux 系统中沿用了这种文件结构,所有目录和文件都在“根目录”下,目录名为/
FHS(文件系统层次标准)定义了在根目录下的主要目录以及每个目录应该存放什么文件。
下面进入根目录中,查看一下 Linux 安装后默认的目录,如下所示:

1
2
3
4
➜  / ls
bin   etc   lib64       mnt         proc  sbin  tmp
boot  home  lost+found  root  srv   usr
dev   lib   media       opt         run   sys   var

根据 FHS 的定义,每个目录应该放置的文件如表所示:

目录 目录的用途
/bin 常见的用户指令
/boot 内核和启动文件
/dev 设备文件
/etc 系统和服务的配置文件
/home 系统默认的普通用户的家目录
/lib 系统函数库目录
/lost+found ext3 文件系统需要的目录,用于磁盘检查
/mnt 系统加载文件系统时常用的挂载点
/opt 第三方软件安装目录
/proc 虚拟文件系统
/root root 用户的家目录
/sbin 存放系统管理命令
/tmp 临时文件的存放目录
/usr 存放于用户直接相关的文件和目录
/media 系统用来挂载光驱等临时文件系统的挂载点

列出文件树

mac:

brew install tree

绝对路径和相对路径

正如前文所述,Linux 系统采用了目录树的文件组织结构,在 Linux 下每个目录或文件都可以从根目录处开始寻找,比如:/usr/local/src目录。
这种从根目录开始的全路径被称为“绝对路径”,绝对路径一定是以/开头的。

想要确定当前所在的目录,可以使用pwd命令查看:

1
2
➜  ~ pwd
/root

在每个目录下,都会固定存在两个特殊目录,分别是一个点.和两个点..的目录。
一个点.代表的是当前目录,两个点..代表的是当前目录的上层目录。
在 Linxu 下,所以以点开始的文件都是“隐藏文件”,对于这类文件,只使用命令ls -l是看不到的,必须要使用ls -la才可以看到

1
2
3
4
5
➜  ~ cd /mnt
➜  /mnt ls -al
total 8
drwxr-xr-x.  2 root root 4096 Apr 11  2018 .
dr-xr-xr-x. 20 root root 4096 Aug 31 09:07 ..

顾名思义,“相对路径”的关键在于当前在什么路径下。
假设当前目录在/usr/local下,那么它的上层目录(/usr目录)可以用../表示,而/usr/local的下层目录(src)则可以用./src表示。
前面讲到的...目录实际上也是属于相对路径

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
➜  ~ cd /mnt
➜  /mnt ls -la
总用量 8
drwxr-xr-x.  2 root root 4096 4月  11 2018 .
dr-xr-xr-x. 20 root root 4096 8月  31 09:07 ..
➜  /mnt cd .
➜  /mnt pwd
/mnt
➜  /mnt cd ..
➜  / pwd
/

文件的相关操作

Linux 遵循一切皆文件的规则,对 Linux 进行配置时,在很大程度上就是处理文件的过程,所以掌握文件的相关操作是非常有必要的。

创建文件:touch

在 Linux 中创建一个文件,只需要进入相关目录,然后使用 touch 命令即可,参数为想要创建文件的文件名。
比如说,在/tmp目录中创建一个 test.txt 文件:

1
2
3
4
➜  ~ cd /tmp
➜  /tmp touch test.txt
➜  /tmp ls -l
-rw-r--r--  1 root root    0 8月  31 21:19 test.txt

事实上,如果在使用 touch 命令创建文件的时候,当前目录中已经存在了这个文件,那么这个命令不会对当前的同名文件造成影响,因为它并不会修改文件的内容,虽然实际上 touch 确实对该文件做了“修改”--它会更新文件的创建时间树形。
比如说,在当前目录下我们继续使用 touch test.txt 命令,然后观察该文件时间树形部分的变化:

1
2
3
➜  /tmp touch test.txt
➜  /tmp ls -l
-rw-r--r--  1 root root    0 8月  31 21:25 test.txt

就会发现创建时间已经被修改了

查看文件:cat

该命令是 concatenate 的简写,用户查看文件内容,后面跟上要查看的文件名即可
加上-n参数可以显示每行的行号

查看文件头:head

有时候文件非常大,使用 cat 命令显示出来的内容太多,而我们可能并不想查看所有内容,这时候就可以使用 head 命令了,后面跟上需要查看的文件名就可以了。
默认情况下,head 将显示该文件前 10 行的内容。继续拿 install.log 这个文件举例,如下所示

1
2
3
head install.log
# 也可以使用 -n 参数指定显示的行数
head -n 20 install.log

查看文件尾:tail

tail 命令与 head 命令非常类似。当文件很大时,可以使用该命令查看文件尾部的内容,默认情况下 tail 也是只显示文件的最后 10 行内容,同样可以使用 -n 参数指定显示的行数。

但是 tail 还有个更实用的功能,就是可以动态地查看文件尾。
这对查看一些不断改变的文件来说非常有用。
比如说,系统中会有很多日志文件,这些文件是会随时变化的(具体地说,就是随时会有新的日志写入),要动态地查看这些文件,使用-f参数就可以做到。
举个例子,/var/log/message文件是默认的系统日志文件,系统在运行中将会有大量的日志写入这个文件中,可以使用如下的命令,一旦有新的日志内容写入,该命令会立即将新内容显示出来

tail -f /var/log/messages

文件格式转换:dos2unix

该命令是 DOS to UNIX 的简写,也许你从字面上可以大概猜到它的作用,就是可以把 DOS 格式的文本文件转变成 UNIX 下的文本文件。
之所以有这样的需求是因为 Linux 和 Windows 系统是可以通过文件共享的方式共享文件的,当把 Windows 下的文本文件移动到 Linux 下时,会由于系统之间文件文件的换行符不同而造成文件在 Linux 下的读写操作有问题。
该命令的使用方式非常简单直接,后面跟上需要转换的文件名即可。

目录的相关操作

cd: 该命令是 change directory 的简写,方便用户切换到不同的目录

创建目录:mkdir

该命令是 make directory 的简写,其用途是创建目录,使用方法是在后面跟上目录的名称

可以使用 -p 参数一次性创建所有目录,这样就不用费力地一个个创建了。
-p 参数: 帮助你直接将所需要的目录(包含上层目录)递归创建

mkdir -p dir3/dir4

也可以使用绝对路径的方式来创建: mkdir -p /root/dir1/dir2/dir3/dir4

删除目录:rmdir 和 rm

该命令是 remove directory 的简写,用来删除目录。但是需要注意的是,它只能删除空目录,如果目录不为空(存在文件或者子目录),那么该命令将拒绝删除指定的目录。
继续上例,假设目前所在的目录是/root/dir1/dir2,当前目录下存在dir3,但 dir3 不为空,因为包含 dir4,我们试图使用该命令来删除 dir3,结果如下:

1
2
rmdir dir3/
rmdir: dir3/: Directory not empty

这里系统给出的信息是,dir3 非空。
如果先将 dir3 中的 dir4 目录删除,然后再删除 dir3 看看是否可行:

1
2
rmdir dir3/dir4
rmdir dir3

如果使用 rm 来删除目录,只需要使用一个 -r 参数就可以做到,如下所示:

1
rm -r dir1

如果 dir1 中有数百个文件,那我们就需要不厌其烦地输入y来确认。
在使用 rm 删除目录时,最常用的组合参数是-rf,这样就不会有任何提醒了。可直接将目录删除干净。
但是使用这个命令要极其小心,因为一旦删除了几乎是不可能恢复的。
另外,由于 root 用户在 Linux 系统中的权限非常高,甚至可以用rm -rf/命令来删除全部的系统文件(这样做的后果是灾难性的),所以-rf参数删除目录一定要慎之又慎。

文件和目录复制: cp

该命令是 copy 的简写,用于复制文件和目录。
如果是复制文件,其后接两个参数,第一个参数是要复制的源文件,第二个参数是要复制到的目录或复制后的文件名。

复制目录只需要使用 -r 参数即可

文件时间戳

通过 touch 可以创建新文件。如果文件已经存在,那么 touch 命令仅仅会更新文件的创建时间而不会修改文件内容。
请记住,在 Linux 下目录也是一种文件,所有如果 touch 一个目录,这个目录的创建时间也会被更新。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
➜  /tmp mkdir touch_dir1
➜  /tmp touch touch_file1
➜  /tmp ll
drwxr-xr-x  2 root root 4.0K 8月  31 22:24 touch_dir1
-rw-r--r--  1 root root    0 8月  31 22:24 touch_file1

➜  /tmp touch touch_dir1 touch_file1
➜  /tmp ll
drwxr-xr-x  2 root root 4.0K 8月  31 22:26 touch_dir1
-rw-r--r--  1 root root    0 8月  31 22:26 touch_file1

不管是哪种系统,几乎所有的程序都会读写系统文件,默认情况下,一旦发生写文件操作,该文件的时间戳将会立刻得到更新。
因此可以利用这种特性来有选择性的备份一些文件(又叫差异备份)。
比如有一个目录中有若干个文件,我们每天需要备份一次。最简单的办法是每天使用 cp 操作全部备份一次,但是这种做法在文件总大小比较大的情况下会显得效率不高。
如果有一些文件很大,但是和上一次备份相比并没有发生任何变化,实际上是不需要进行备份的,只需要找出在上一次备份之后发生变化的文件,然后备份这些文件即可。

为了演示利用时间戳来急性差异化备份,下面先创建一些目录和文件:

1
2
3
4
mkdir org_dir # 这是要备份的目录
➜  ~ mkdir bak_dir # 这是备份存放目录
➜  ~ cd org_dir # 进入要备份的目录
➜  org_dir touch a b c # 创建几个文件

第一次备份自然是需要复制 org_dir 下的所有文件到 bak_dir 中:

1
2
3
➜  org_dir cp * ../bak_dir
# 复制当前目录下的所有文件到上层目录的 bak_dir 目录中
# 这里用到了一个星号 *,代表所有,还用到了相对目录

复制完成后,在这个目录中利用 touch 命令创建出一个新文件 time_stamp,注意看一下,该文件和其他文件的时间戳是不一样的,time_stamp 文件的创建时间自然是比其他的文件要晚。
下次备份的时候,只需要找出比 time_stamp 文件时间戳新的文件,然后备份该文件即可,如下所示:

1
2
3
4
5
6
7
➜  org_dir touch time_stamp
➜  org_dir ll
总用量 0
-rw-r--r-- 1 root root 0 8月  31 23:27 a
-rw-r--r-- 1 root root 0 8月  31 23:27 b
-rw-r--r-- 1 root root 0 8月  31 23:27 c
-rw-r--r-- 1 root root 0 8月  31 23:50 time_stamp

假设再一次备份的时候,文件 a 是被更新过了的,这里使用 touch 来模拟一下这个场景:使用 touch 命令发现 a 文件的时间戳比 time_stamp 要新,那么可知 a 被程序修改过了,而其他的文件(文件 b 和 文件 c)并没有被更新,这时只需要备份 a 文件就可以了。
备份完成后,还需要继续 touch 一下 time_stamp,以更新该文件的时间戳,在下次备份的时候只需要找比这个文件时间戳更新的文件即可

1
2
3
4
5
6
7
8
9
➜  org_dir touch a
➜  org_dir ll
总用量 0
-rw-r--r-- 1 root root 0 8月  31 23:55 a
-rw-r--r-- 1 root root 0 8月  31 23:27 b
-rw-r--r-- 1 root root 0 8月  31 23:27 c
-rw-r--r-- 1 root root 0 8月  31 23:50 time_stamp
➜  org_dir cp a ../bak_dir
➜  org_dir touch time_stamp

这里只是举一个例子来说明文件时间戳来做备份的原理,在实际工作中必须将这种过程脚本化、自动化。
因为人为地备份文件一方面容易出错,另一方面也是不现实的:想象一下如果需要备份的文件有成百上千个,人工地逐个比较文件的时间戳是不可能的。

文件状态信息

1
2
3
4
5
6
7
8
9
$ stat xxxx
  File: xxxx
  Size: 12969736        Blocks: 25336      IO Block: 4096   regular file
Device: fd00h/64768d    Inode: 7100092     Links: 2
Access: (0755/-rwxr-xr-x)  Uid: (    0/    root)   Gid: (    0/    root)
Access: 2021-03-01 05:01:33.682579392 +0000
Modify: 2021-03-01 04:59:20.769151419 +0000
Change: 2021-03-01 04:59:20.845149228 +0000
 Birth: -

文件和目录的权限

可能大家早就有所耳闻,Linux 系统之所以更安全,是因为对文件权限有着非常严格的控制。

查看文件或目录的权限:ls -al

其中-l参数表示要求列出每个文件的详细信息,-a参数则要求 ls 命令还要同时列出隐藏文件。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
➜  / ls -al
总用量 76
dr-xr-xr-x. 20 root root  4096 8月  31 09:07 .
dr-xr-xr-x. 20 root root  4096 8月  31 09:07 ..
-rw-r--r--   1 root root     0 8月  18 2017 .autorelabel
lrwxrwxrwx   1 root root     7 8月   5 18:32 bin -> usr/bin
dr-xr-xr-x.  5 root root  4096 8月   5 18:36 boot
drwxr-xr-x  19 root root  2980 4月  11 2018 dev
drwxr-xr-x. 84 root root  4096 8月   5 18:38 etc
drwxr-xr-x.  3 root root  4096 4月  11 2018 home
lrwxrwxrwx   1 root root     7 8月   5 18:32 lib -> usr/lib
lrwxrwxrwx   1 root root     9 8月   5 18:32 lib64 -> usr/lib64
drwx------.  2 root root 16384 8月  18 2017 lost+found
drwxr-xr-x.  2 root root  4096 4月  11 2018 media
drwxr-xr-x.  2 root root  4096 4月  11 2018 mnt
drwxr-xr-x.  2 root root  4096 4月  11 2018 opt
dr-xr-xr-x  84 root root     0 4月  11 2018 proc
dr-xr-x---. 13 root root  4096 9月   1 08:40 root
drwxr-xr-x  23 root root   620 8月   5 18:38 run
lrwxrwxrwx   1 root root     8 8月   5 18:32 sbin -> usr/sbin
drwxr-xr-x.  2 root root  4096 4月  11 2018 srv
dr-xr-xr-x  13 root root     0 4月  11 2018 sys
drwxrwxrwt. 12 root root  4096 9月   1 08:39 tmp
drwxr-xr-x. 14 root root  4096 8月   5 18:32 usr
drwxr-xr-x. 19 root root  4096 8月   5 18:32 var

正如大家所见,ls -al格式化地输出了文件的详细信息,每个文件都有 7 列输出,下面详细介绍每列的含义

第一列是文件类别和权限,这列由 10 个字符组成,第一个字符表明该文件的类型。

第一个字符可能的值 含义
d 目录
- 普通文件
l 链接文件
b 块文件
c 字符文件
s socket 文件
p 管道文件

接下来的属性中,每 3 个字符为一组,第 2 到 4 个字符代表该文件所有者(user)的权限,第 5~7 个字符代表给文件所有组(group)的权限,第 8~10 个字符代表其他用户(others)拥有的权限。
每组都是rwx组合,如果拥有读权限,则该组的第一个字符显示r,否则显示一个小横线;
如果拥有写权限,则该组的第二个字符显示w,否则显示一个小横线;
如果拥有执行权限,则第三个字符显示 x,否则显示一个小横线

第二列代表“连接数”,除了目录文件之外,其他所有文件的连接数都是 1,目录文件的连接数是该目录中包含其他目录的总个数 +2,也就是说,如果目录 A 中包含目录 B 和 C,则目录 A 的连接数为 4

第三列代表该文件的所有人,第四列代表该文件的所有组,第五列是该文件的大小,第六列是该文件的创建时间或最近的修改时间,第七列是文件名。

文件隐藏属性

Linux 下的文件还有一些隐藏属性,必须使用 lsattr 来显示,默认情况下,文件的隐藏属性都是没有设置的。
查看文件的隐藏属性需要使用 lsattr 命令,如下所示:

1
2
lsattr anaconda-ks.cfg
-------------anaconda-ks.cfg

结果中的第一列是 13 个小短横,其中每一个小横线都是一个属性,如果当前位置上设置了该属性就会显示相对应的字符。

如果要设置文件的隐藏属性,需要使用 chattr 命令。
这里介绍几个常用的隐藏属性,第一种是 a 属性。拥有这种属性的文件只能在尾部增加数据而不能被删除。
下面使用 chattr 来给该文件添加 a 属性:

1
2
3
4
5
6
chattr +a anaconda-ks.cfg
ls attr anaconda-ks.cfg
-----a-------anaconda-ks.cfg
rm anaconda-ks.cfg
rm: remove regular file 'anaconda-ks.cfg'? y
rm: cannot remove 'anaconda-ks.cfg': Operation not permitted

如上所示,设置了 a 属性的文件,即便是 root 用户也不能删除它,但是实际上可以以尾部新增的方式继续向该文件中写入内容

还有一种比较常用的属性是 i 属性。
设置了这种属性的文件将无法写入、改名、删除,即便是 root 用户也不行。
这种属性常用于设置在系统或者关键服务中的配置文件,这对提升系统安全性有较大的帮助。

更多隐藏属性请使用 man chattr 查看

改变文件权限:chmod

Linux 下的每个文件都定义了文件拥有者(user)、拥有组(group)、其他人(others)的权限,我们使用字母 u、g、o 来分别代表拥有者、拥有组、其他人,而对应的具体权限则使用 rwx 的组合来定义,增加权限使用 + 号,删除权限使用 - 号,详细权限使用 = 号。

下表用一些例子说明了如何使用 chmod 来改变文件的权限

作用 命令
给某文件添加用户读权限 chmod u+r somefile
给某文件删除用户读权限 chmod u-r somefile
给某文件添加用户写权限 chmod u+w somefile
给某文件删除用户写权限 chmod u-w somefile
给某文件添加用户执行权限 chmod u+x somefile
给某文件删除用户执行权限 chmod u-x somefile
添加用户对某文件的读写执行权限 chmod u+rwx somefile
删除用户对某文件的读写执行权限 chmod u-rwx somefile
给某文件设定用户拥有读写执行权限 chmod u=rwx somefile

如果要给用户组或其他人添加或删除相关权限,只需要将上面的 u 相应地更换为 g 或 o 即可。
但是正如大家看到的,这种方式同一时刻只能给文件拥有者、文件拥有组或是其他所有人设置权限,如果要想同时设置所有人的权限就需要使用数字表示法了,我们定义 r=4,w=2,x=1,如果权限是 rwx,则数字表示 7,如果权限是 r-x,则数字表示为 5.
假设想设置一个文件权限是:拥有者的权限是读、写、执行(rwx),拥有组的权限是读、执行(r-x),其他人的权限是只读(r--),那么可以使用命令 chmod 754 somefile 来设置

如果需要修改的不是一个文件而是一个目录,以及该目录下所有的文件、子目录、子目录下所有的文件和目录(即递归设置该目录下所有的文件和目录的权限),则需要使用-R参数,也就是chmod -R 754 somedir

使用数字表示法设置权限是很常用的方式,一定要熟练掌握

改变文件的拥有者:chown

该命令用来更改文件的拥有者,同时它也具备更改文件拥有组的功能。
默认情况下,使用什么用户登录系统,那么该用户新创建的文件和目录的拥有者就是这个用户,比如使用 root 账户登录后,创建一个文件 a.txt,那么该文件的拥有者是 root 用户,如下所示:

1
2
3
➜  ~ touch a.txt
➜  ~ ll a.txt
-rw-r--r-- 1 root root 40 9月   1 22:14 a.txt

要是想改变该文件的拥有者该怎么办呢?可使用 chown 命令将该文件的拥有者更改为 john(假设系统中有这个用户):

1
2
3
chown john a.txt
ls -l a.txt
-rw-r--r-- 1 john root 0 Jan 4 19:37 a.txt

该命令还可以同时更改文件的用户组。继续将该文件改为 john 用户组,使用方式如下:

1
2
3
chown :john a.txt
ls -l a.txt
-rw-r--r-- john john 0 Jan 4 21:00 a.txt

以上两步可以使用一条命令同时设置:

1
chown john:john a.txt

如果需要修改的不是一个文件而是一个目录,以及该目录下所有的文件、子目录、子目录下所有文件和目录(即递归设置该目录下所有的文件和目录的拥有者是 john),则需要使用 -R 参数,也就是chown -R john somedir; 如果要同时修改用户组为 john,则使用chown -R john:john somedir

改变文件的拥有组:chgrp

该命令用来更改文件的拥有组。下面将新创建的文件 b.txt 修改用户组为 john

1
2
3
4
5
6
touch b.txt
ls -l b.txt
-rw-r--r-- 1 root root 0 Jan 4 21:09 b.txt
chgrp john b.txt
ls -l b.txt
-rw-r--r-- 1 root john 0 Jan 4 21:10 b.txt

如果需要修改的不是一个文件而是一个目录,以及该目录下所有的文件、子目录、子目录下所有的文件和目录(即递归设置该目录下所有的文件和目录的拥有组是 john),则需要使用 -R 参数,也就是chgrp -R john somedir

文件特殊属性:SUID/SGIO/Sticky

每个用户都可以使用 passwd(该命令的绝对路径是/usr/bin/passwd)来修改自己的秘密。
系统用户记录用户信息和密码的文件分别是/etc/passwd/etc/shadow中对应用户的密码。
对于这个文件,只有 root 用户有读权限,而普通用户在修改自己的密码时,最终也会修改这个文件。
注意,虽然/etc/shadow文件对于 root 用户来说只有读权限,但是实际上 root 是可以使用强写的方式来更新这个文件的。
但是普通用户在运行这个命令时居然有权限来写/etc/shadow文件,怎么可能呢?先来确认一下/etc/passwd/etc/shadow的文件属性,从而确定普通用户根本没有写权限:

1
2
3
4
ls -l /etc/passwd
-rw-r--r-- 1 root root 1379 Dec 10 04:01 /etc/passwd
ls -l /etc/shadow
-r-------- 1 root root 859 Dec 10 04:41 /etc/shadow

再来看一下/usr/bin/passwd的权限,发现有个特别的 s 权限在用户权限上,这就是奥秘所在--该命令是设置了 SUID 权限的,这意味着普通用户可以使用 root 的身份来执行这个命令。
但是必须注意的是,SUID 权限只能用于二进制文件。

1
2
➜  ~ ll /usr/bin/passwd
-rwsr-xr-x 1 root root 28K 4月   1 11:57 /usr/bin/passwd

下面是给一个二进制文件添加 SUID 权限的方法:

chmod u+s somefile

介绍完 SUID 之后,想必再来理解 SGID 就很容易了:如果某个二进制文件的用户组权限被设置了 s 权限,则该文件的用户组中所有的用户将都能以该文件的用户身份去运行这个命令,一般来说 SGID 命令在系统中用得很少,给一个二进制文件添加 SGID 权限的方法如下:

chmod g+s somefile

Sticky 权限只能用于设置在目录上,设置了这种权限的目录,任何用户都可以在该目录中创建或修改文件,但是只有该文件的创建者和 root 可以删除自己的文件。
RedHat 或 CentOS 系统中的/tmp目录就拥有 Sticky 权限(注意看权限的最后是 t),如下所示:

1
2
ll -d /tmp/
drwxrwxrwt 3 root root 4096 Jan 4 04:53 /tmp/

举个例子,用户 john 登录后,在 /tmp 下创建了一个文件 john_file,然后,用户 jack 也登录到系统中进入 /tmp 目录,他试图删除这个文件,系统会告诉他没有权限删除这个文件,如下所示:

1
2
3
4
5
6
7
8
# 用户登录到系统中并创建了 /tmp/john_file
cd /tmp/
touch john_file
# 用户 jack 登录到系统中试图删除 /tmp/john_file
cd /tmp
rm john_file
rm: remove write_protected regular empty file 'john_file'? y
rm: cannot remove 'john_file': Operation not permitted

给一个目录添加 t 权限的方式如下:

chmod o+t somedir

默认权限和 umask

既然说 Linux 系统对每个文件都有严格的权限控制,但是似乎到目前为止还没有太细致地设置文件权限,而且在新创建文件的时候,也没有特意设置过权限。
事实上,所有的文件在创建时都是有权限的了,那么这些权限是怎么来的呢?
也许你会想到是系统采用了默认权限的方法,也就是当我们创建文件的时候,系统套用默认权限来设置了文件。
下面使用 root 用户登录系统来看一下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
➜  ~ touch root_file1
➜  ~ touch root_file2
➜  ~ ls -l root_file1
-rw-r--r-- 1 root root 0 9月   2 21:37 root_file1
➜  ~ ls -l root_file2
-rw-r--r-- 1 root root 0 9月   2 21:37 root_file2
➜  ~ mkdir root_dir1
➜  ~ mkdir root_dir2
➜  ~ ls -ld root_dir1
drwxr-xr-x 2 root root 4096 9月   2 21:37 root_dir1
➜  ~ ls -ld root_dir2
drwxr-xr-x 2 root root 4096 9月   2 21:39 root_dir2

注意,创建的 root_file1、root_file2 文件的权限都是 644;创建的 root_dir1、root_dir2 目录的权限都是 755.
到这里似乎可以得出一个结论:文件的默认权限是 644,目录的默认权限是 755。但是实际情况是这样的吗?
让我们使用普通用户 john 来操作一下,如下所示:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
➜  ~ touch john_file1
➜  ~ touch john_file2
➜  ~ ls -l john_file1
-rw-rw-r-- 1 root root 0 9月   2 21:37 john_file1
➜  ~ ls -l john_file2
-rw-rw-r-- 1 root root 0 9月   2 21:37 john_file2
➜  ~ mkdir john_dir1
➜  ~ mkdir john_dir2
➜  ~ ls -ld john_dir1
drwxrwxr-x 2 root root 4096 9月   2 21:37 john_dir1
➜  ~ ls -ld john_dir2
drwxrwxr-x 2 root root 4096 9月   2 21:39 john_dir2

这里创建的 john_file1、john_file2 文件的权限都是 664;
创建的 john_dir1、john_dir2 目录的权限都是 775.

可以给出一个结论:对于 root 用户,文件的默认权限是 644,目录的默认权限是 755;
对于普通用户,文件的默认权限是 664,目录的默认权限是 775。
到这里似乎可以结束关于默认权限的讨论了。但是,有两个疑问需要考虑一下:

  • 这个默认权限是从哪里来的呢?
  • 为什么 root 用户和普通用户的默认权限不一样呢?

要想回答上面的问题,就需要引入 umask 概念,中文翻译为:遮罩。
在 Linux 下,定义目录创建的默认权限的值是“umask遮罩 777 后的权限”,定义文件创建的默认权限是“umask 遮罩 666 后的权限”。

系统在/etc/profile文件中,通过这样一段代码设置了不同用户的遮罩值

1
2
3
4
5
if [ $UID -gt 199 ] && [ "`/usr/bin/id -gn`" = "`/usr/bin/id -un`" ]; then
    umask 002
else
    umask 022
fi

从上面的代码中可以看出,UID 大于 199 的用户设置了 umask 为 002,否则为 022。所以 umask 值对于 root 用户是 022,对于普通用户是 002,这也就造成了上面我们看到的 root 用户和普通用户创建出来的文件和目录默认权限不一样,那么如何使用遮罩计算权限呢?

777 用字符串表示为rwxrwxrwx,如果遮罩值是 022,用字符串表示为----w--w-,那么前者第五位和第八位的 w 被遮罩掉,权限变为rwxr-xr-x,用数字表示就是 755。
如果遮罩值是 002,用字符串表示为:-------w-,那么第八位的 w 被遮罩掉,权限变为rwxrwxr-x,用数字表示就是 775.

666 用字符串表示位rw-rw-rw-,如果遮罩值是 022,用字符串表示位----w--w-,那么前者第 5 位和第 8 位的 w 被遮罩掉,权限变为rw-r--r--,用数字表示就是 644。
如果遮罩值是 002,用字符串表示为:-------w-,那么第 8 位的 w 被遮罩掉,权限变为 rw-rw-r--,用数字表示就是 644.

特别强调一下,网络上有很多关于计算 umask 遮罩后权限值的讲解,比较主流但是错误的讲解方式是使用“同位相减”的做法来计算遮罩后的值,比如说 777-022 同位相减得到 755,666-022 同位相减得到 644,这种看似正确的结果其实只是一种巧合,并不是了解遮罩的正确方式。
假设有个文件的权限位 666,在遮罩值为 011 的情况下,采用该“同位相减”的方法计算出的权限值为 655,但实际上正确的权限值应该是 666.

查看文件类型:file

使用 ls -l 命令可以通过查看第一个字符判断文件类型。
字母 d 代表目录、字母 l 代表连接文件,字母 b 代表块文件,字母 c 代表字符文件,字母 s 代表 socket 文件,字符 - 代表普通文件,字母 p 代表管道文件,而 file 命令则可以直接告诉我们文件类型,还能给出更多的文件信息,如下所示:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
➜  ~ file /root
/root: directory
➜  ~ file /tmp
/tmp: sticky directory
➜  ~ ls -l /etc/passwd
-rw-r--r-- 1 root root 1216 6月  29 2019 /etc/passwd
➜  ~ file /etc/passwd
/etc/passwd: ASCII text

➜  ~ ls -l /usr/bin/passwd
-rwsr-xr-x 1 root root 27856 4月   1 11:57 /usr/bin/passwd
➜  ~ file /usr/bin/passwd
/usr/bin/passwd: setuid ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.32, BuildID[sha1]=dee1b9ab6618c6bfb84a14f85ba258c742cf4aec, stripped

命令与文件的查找

操作系统中有成千上万的文件散落在文件系统的各个角落中,还有不同用户创建的各种文件。
随着系统的运行,文件数会越来越多,要想记住所有的文件在什么位置是不可能的。
可能大家已经熟悉了在 Windows 下使用搜索工具来查找文件,但是在 Linux 系统下由于主要使用的是字符界面(图形界面上也没有类似的工具),那么查找文件就只能通过一些查找命令来进行了。

查找执行文件:which/whereis

which 用于从系统的 PATH 变量所定义的目录中查找可执行文件的绝对路径,使用方法如下:

1
2
➜  ~ which passwd
/usr/bin/passwd

使用 whereis 也能查到其路径,但是和 which 不同的是,它不但能找出其二进制文件,还能找出相关的 man 文件

1
2
➜  ~ whereis passwd
passwd: /usr/bin/passwd /etc/passwd /usr/share/man/man1/passwd.1.gz
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
➜  ~ which ls
ls: aliased to ls -G
➜  ~ which ifconfig
/sbin/ifconfig
➜  ~ which pwd
pwd: shell built-in command
➜  ~ which rm
/bin/rm
➜  ~ which python
/Users/nocilantro/anaconda3/bin/python
➜  ~ which node
/usr/local/bin/node
➜  ~ which ipython
/Users/nocilantro/anaconda3/bin/ipython
➜  ~ which pip
/Users/nocilantro/anaconda3/bin/pip
➜  ~ which code
/usr/local/bin/code
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
➜  ~ cat /etc/centos-release
CentOS Linux release 7.3.1611 (Core)

➜  ~ whereis -l
bin: /usr/bin
bin: /usr/sbin
bin: /usr/lib
bin: /usr/lib64
bin: /etc
bin: /usr/etc
bin: /usr/games
bin: /usr/local/bin
bin: /usr/local/sbin
bin: /usr/local/etc
bin: /usr/local/lib
bin: /usr/local/games
bin: /usr/include
bin: /usr/local
bin: /usr/libexec
bin: /usr/share
bin: /root/.pyenv/shims
bin: /root/.pyenv/bin
man: /usr/share/man/man6
man: /usr/share/man/ru
man: /usr/share/man/ko
man: /usr/share/man/man7x
man: /usr/share/man/cs
man: /usr/share/man/pl
man: /usr/share/man/man4
man: /usr/share/man/man8x
man: /usr/share/man/man1
man: /usr/share/man/man3x
man: /usr/share/man/man1x
man: /usr/share/man/ja
man: /usr/share/man/sv
man: /usr/share/man/hu
man: /usr/share/man/man2
man: /usr/share/man/man4x
man: /usr/share/man/id
man: /usr/share/man/man8
man: /usr/share/man/man7
man: /usr/share/man/man9x
man: /usr/share/man/man1p
man: /usr/share/man/man9
man: /usr/share/man/man0p
man: /usr/share/man/zh_TW
man: /usr/share/man/sk
man: /usr/share/man/man3
man: /usr/share/man/da
man: /usr/share/man/man5x
man: /usr/share/man/tr
man: /usr/share/man/zh_CN
man: /usr/share/man/man3p
man: /usr/share/man/de
man: /usr/share/man/man2x
man: /usr/share/man/man5
man: /usr/share/man/fr
man: /usr/share/man/mann
man: /usr/share/man/it
man: /usr/share/man/man6x
man: /usr/share/man/pt
man: /usr/share/man/es
man: /usr/share/man/pt_BR
src: /usr/src/debug
src: /usr/src/kernels

Mac下执行命令:

1
2
3
4
5
6
➜  ~ whereis ls
/bin/ls
➜  ~ whereis rm
/bin/rm
➜  ~ whereis python
/usr/bin/python

数据库查找:locate

与 find 不同,locate 命令依赖于一个数据库文件,Linux 系统默认每天会检索一下系统中的所有文件,然后将检索到的文件记录到数据库中。
在运行 locate 命令的时候可直接到数据库中查找记录并打印到屏幕上,所以使用 locate 命令要比 find 命令反馈更为迅速。
在执行这个命令之前一般需要执行 updatedb 命令(这不是必须的,因为系统每天会自动检索并更新数据库信息,但是有时候会因为文件发生了变化而系统还没有再次更新而无法找到实际上确实存在的文件。所以有时候需要主动运行该命令,以创建最新的文件列表数据库),以及时更新数据库记录。
下面是使用 locate 命令来查找 httpd.conf 文件:

1
2
3
updatedb
locate httpd.conf
/etc/httpd/conf/httpd.conf

为了更好地理解 locate 的工作原理,这里展示一个实验:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
(base) ➜  ~ touch test_locate
(base) ➜  ~ find / -name test_locate
/root/test_locate
(base) ➜  ~ locate test_locate # 没找到!
(base) ➜  ~ updatedb # 更新数据库
(base) ➜  ~ locate test_locate # 找到了
/root/test_locate
(base) ➜  ~ rm test_locate
(base) ➜  ~ locate test_locate
/root/test_locate
(base) ➜  ~ updatedb
(base) ➜  ~ locate test_locate
(base) ➜  ~

这个实验表明,locate 命令依赖于其用于记录文件的数据库,该数据库需要使用 updatedb 来更新。
当然,系统每天也会自动运行一次,但是不必等系统运行,必要的时候可主动进行手动更新。

一般查找:find

在 Linux 下面也有相当优异的查找命令,通常 find 不很常用。除速度慢之外,也影响磁盘性能。
一般我们都是先使用 whereis 或是 locate 来检查,如果真的找不到了,才以 find 来查找。

为什么?因为 whereis 只找系统中某些特定目录下面的文件而已,locate 则是利用数据库来查找文件名,当然两者就相当的快速,并且没有实际查找硬盘内的文件系统状态,比较省时间。

这个命令言简意赅地道出了其作用,不需要更多解释。在某个路径下查找文件的方法如下:

1
find PATH -name FILENAME

假设需要在系统中找到一个名为 httpd.conf 的文件,可以这么写:

1
find / -name httpd.conf

这条命令的意思是,从根目录开始寻找名为 httpd.conf 的文件。
由于是从根目录开始寻找,find 命令会遍历/下的所有文件,然后打印出寻找结果。
如果你有点经验,大概知道这个文件可能存在于/etc下,因为看起来这是一个配置文件,这时便可以优化一下查找语句,这样耗时会更少一点。
命令如下所示:

1
find /etc -name httpd.conf

可以使用星号通配符来模糊匹配要查找的文件名,比如想找出系统中所有以.conf结尾的文件,或以 httpd 开头的文件:

1
2
find / -name *.conf
find / -name httpd*

其实 find 还有很多参数可以使用

参数 含义
-name filename 查找文件名为 filename 的文件
-perm 根据文件权限查找
-user username 根据用户查找
-mtime -n/+n 查找 n 天内/n 天前更改过的文件
-atime -n/+n 查找 n 天内/n 天前访问过的文件
-ctime -n/+n 查找 n 天内/n 天前创建的文件
-newer filename 查找更改时间比 filename 新的文件
-type b/d/c/p/l/f/s 查找块/目录/字符/管道/链接/普通/套接字文件
-size 根据文件大小查找
-depth n 最大的查找目录深度

文件压缩和打包

记得在使用软盘的时期,我经常为需要复制的文件大于 1.44MB 而感到麻烦,因为那时候单张软盘最大的容量只有 1.44MB。
这时通常要将文件做一下压缩处理,运气好的话压缩后就可以复制到一张软盘上了,但是大多数情况下即便文件经过压缩还是很大,这时还得借用分卷工具将压缩过的文件分成若干个小文件,再使用多张软盘复制。

随着科技的发展,现在的存储设备的容量越来越大,虽然在日常生活中家用计算机已经不太需要通过压缩文件的方式来获得更多的空间,但是在服务器环境中,还是非常必要的。
比如,有很多应用非常繁忙,每天的交易巨大,所产生的日志动辄上百 GB,但是由于数据本身是有意义的,不能简单地删除,所以通过压缩的方式来存储日志是非常普遍的做法。
且文本类的日志文件之压缩比往往能达到 80% 到 90%,这样节省下来的空间是非常可观的。

zip

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
➜  ~ touch a.txt
➜  ~ mkdir test_dir
➜  ~ mv a.txt test_dir
➜  ~ ls
test_dir
➜  ~ zip -r test.zip test_dir
  adding: test_dir/ (stored 0%)
  adding: test_dir/a.txt (stored 0%)
➜  ~ ls
test.zip  test_dir
➜  ~ rm -rf test_dir
➜  ~ unzip test.zip
Archive:  test.zip
   creating: test_dir/
 extracting: test_dir/a.txt
➜  ~ ls
test.zip  test_dir

gzip/gunzip

gzip/gunzip 是用来压缩和解压缩单个文件的工具,使用方法比较简单。
比如,在/root目录下压缩 install.log 文件,压缩后生成的文件是install.log.gz文件,然后再使用 gunzip 文件将其解压即可

1
2
3
4
gzip install.log
ls install.log.gz
install.log.gz
gunzip install.log.gz

tar

tar 不但可以打包文件,还可以将整个目录中的全部文件整合成一个包,整合包的同时还能使用 gzip 的功能进行压缩,比如说把整个/boot目录整合并压缩成一个文件。
一般来说,整合后的包习惯使用.tar作为其后缀名,使用 gzip 压缩后的文件则使用 .gz 作为其后缀名。
因为 tar 有同时整合和压缩的功能,所以可使用.tar.gz作为后缀名,或者简写为.tgz
下面的命令将/boot目录整合压缩成了 boot.tgz 文件:

tar -zcvf boot.tgz /boot

这里-z的含义是使用 gzip 压缩,-c是创建压缩文件(create),-v是显示当前被压缩的文件,-f是指使用文件名,也就是这里的 boot.gz 文件。
-x 标识解压

解压命令如下:

tar -zxvf boot.tgz

上面的命令会直接将 boot.tgz 在当前目录中解压成 boot 目录,-z是解压的意思。
如需要指定压缩后的目录存放的位置,需要再使用 -C 参数。
比如说将 boot 目录解压到/tmp目录中

tar -zxvf boot.tgz -C /tmp

解压.tar文件:

1
tar xvf mysql-server_8.0.25-1ubuntu20.04_amd64.deb-bundle.tar

bzip2

使用 bzip2 压缩文件时,默认会产生以 .bz2 扩展名结尾的文件,这里使用 -z 参数进行压缩,使用 -d 参数进行解压缩

1
2
3
4
bzip2 install.log
ls -l install.log.bz2
install.log.bz2
bzip2 -d install.log.bz2

cpio

该命令一般是不单独使用的,需要和 find 命令一同使用。
当由 find 按照条件找出需要备份的文件列表后,可通过管道的方式传递给 cpio 进行备份,生成 /tmp/conf.cpio 文件,然后再将生成的 /tmp/conf.cpio 文件中包含的文件列表完全还原回去。

1
2
3
4
# 备份
find /etc -name *.conf | cpio -cov > /tmp/conf.cpio
# 还原
cpio --absolute -filenames -icvu < /tmp/conf.cpio