第十七章 文件搜索

前言

  1. 本章我们主要介绍两个用于在 Linux 系统中搜索文件的工具:
  • locate: 通过文件名查找文件
  • find: 在文件系统目录框架中查找文件
  • xargs: 从标准输入中建立、执行命令行
  • touch: 更改文件的日期时间
  • stat: 显示文件或文件系统的状态

locate - 较简单的方式查找文件

  1. locate 命令通过快速搜索数据库,以寻找路径名与给定子字符串相匹配的文件,同时输出所有匹配结果。
  2. 例如,假定查找名称以 zip 字符串开头的程序,由于查找的是程序文件,所以可以认为包含所要查找的程序的目录应以 /bin 结尾。因此,可以尝试下面的命令行。
    1
    [me@linuxbox ~]$ locate bin/zip
  3. 有时搜索需求并不是这么简单,这时便可以用 locate 命令结合其它诸如 grep 这样的工具实现一些更有趣的搜索。
    1
    2
    3
    4
    [me@linuxbox ~]$ locate zip | grep bin
    /bin/bunzip2
    /bin/bzip2
    /bin/bzip2recover
  4. locate 程序已经使用了很长时间,因此出现了多种衍生体。 slocate 和 mlocate 是现代 Linux 发行版本中最常见的两个衍生体,而它们通常都是由名为 locate 的符号链接访问。
  5. 不同版本的 locate 有一些相同的选项设置,而有些版本则包括正则表达式匹配和通配符支持等。

find - 较复杂的方式查找文件

基本介绍

  1. locate 程序查找文件仅仅是依据文件名,而 find 程序则是依据文件的各种属性在既定的目录 (及其子目录) 里查找。
  2. find 最简单的用就是用户给定一个或是更多目录名作为其搜索范围。
  3. 下面就用 find 命令列出当前系统主目录 ~ 下的文件列表清单。
    1
    [me@linuxbox ~]$ find ~
  4. 对于一些比较活跃的用户,一般系统内文件会比较多,使得上述命令输出的列表肯定很长。
  5. 不过,列表信息是以标准形式输出的,所以可以直接将此输出结果作为其他程序的输入。
  6. 如下就是用 wc 程序计算 find 命令搜索到文件的总量。
    1
    2
    [me@linuxbox ~]$ find ~ | wc -l
    47068
  7. find 命令的美妙之处就是可以用来搜索符合特定要求的文件,它通过综合应用 test 选项、action 选项以及 options 选项实现高级文件搜索。

test 选项

  1. 基本介绍
  • 下面让我们首先了解 test 选项。假设我们想要查找的是目录文件,我们可以添加下面的 test 选项达到此目的。
    1
    2
    [me@linuxbox ~]$ find ~ -type d | wc -l
    1695
  • 添加 test 参数 -type d 可以将搜索范围限制为目录,而下面例子中使用 -type f 则表示只对普通文件进行搜索。
    1
    [me@linuxbox ~]$ find ~ -type f | wc -l
  1. find 支持搜索的文件类型
文件类型 描述
b 块设备文件
c 字符设备文件
d 目录
f 普通文件
l 符号链接
  1. 案例
  • 另外我们还可以通过添加其他的 test 项参数实现依据文件大小和文件名的搜索。
  • 如下命令行就是用来查找所有符合 *.JPG 通配符格式以及超过 1 MB 的普通文件。
    1
    [me@linuxbox ~]$ find ~ -type f -name "*.JPG" -size +1M | wc -l
  • 本例中添加的 -name “*.JPG” 的 test 选项表示查找的是符合 .JPG 通配符格式的文件。
  • 注意,这里将通配符扩在上引号中是为了避免 shell 路径名扩展。
  • 另外添加的 -size +1M test 选项,前面的加号表示查找的文件大小比给数值 1M 大。
  • 若字符串前面是减号则表示与给定值完全相等。末尾的 M 是计算单位 MB 的简写
  1. find 支持的计量单位
字母 单位
b 512 字节块 (没有具体说明时的默认值)
c 字节
w 两个字节的字
k KB (每单位包含 1024 字节)
M MB (每单位包含 1048576 字节)
G GB (每单位包含 1073741824 字节)
  1. find 命令的 test 参数
test参数 描述
-cmin n 匹配 n 分钟前改变状态 (内容或属性) 的文件或目录。如果不到 n 分钟,就用 -n ,如果超过 n 分钟,就用 +n
-cnewer file 匹配内容或属性的修改时间比文件 file 更晚的文件或目录
-ctime n 匹配系统中 n*24 小时前文件状态被改变 (内容、属性、访问权限等) 的文件或目录
-empty 匹配空文件及空目录
-group name 匹配属于 name 组的文件或目录。 name 可以描述为组名,也可以描述为该组的 ID 号
-iname pattern 与 -name test 项功能类似只是不区分大小写
-inum n 匹配索引节点是 n 的文件。该 test 选项有助于查找某个特定索引节点上的所有硬件连接
-mmin n 匹配 n 分钟前内容被修改的文件或目录
-mtime n 匹配 n*24 小时前只有内容被更改的文件或目录
-name pattern 匹配有特定通配符模式的文件或目录
-newer file 匹配内容的修改时间比 file 文件更近的文件或目录。这在编写 shell 脚本进行文件备份的时候非常有用。每次创建备份时,更新某个文件 (比如日志) ,然后用 find+ 此参数选项来确定上一次更新后哪个文件改变了
-nouser 匹配不属于有效用户的文件或目录。该 test 可以用来查找哪些属于已删除账户的文件,也可以用来检测攻击者的活动匹配不属于有小组的文件或目录
-perm mode 寻找访问权限与既定模式匹配的文件或目录。既定模式可以用八进制或符号的形式表示
-samefile name 与 -inum test 选项类似。匹配与 file 文件用相同的 inode 号的文件
-size n 匹配 n 大小的文件
-type c 匹配 c 类型的文件
-user name 匹配属于 name 用户的文件和目录。 name 可以描述为用户也可以描述该组的 ID 号

操作符

  1. 基本介绍
  • 即使拥有 find 命令提供的所有 test 参数,我们仍然会需要一个更好的工具来描述 test 参数之间的逻辑关系。
  • 例如,如果我们需要确定某目录下是否所有的文件和子目录都有安全的访问权限,该怎么办?原则上就是去查找那么访问权限不是 0600 的文件和访问权限不是 0700 的子目录。
  • 幸运的是,find 命令的 test 可以结合逻辑操作从而建立具有复杂逻辑关系的匹配条件。
  • 我们可以用下面的命令行来满足上述 find 命令的匹配搜索。
    1
    [me@linuxbox ~]$ find ~ \(-type f -not -perm 0600 \) -or \(-type d -not -perm 0700 \)
  1. find 命令的逻辑操作符
操作符 功能描述
-and (与操作) 查找使该操作符两遍的检验条件都是真的匹配文件。有时直接缩写成 -a 。注意如果两个检测条件之间没有显式的显式操作符, and 就是默认的逻辑关系
-or (或操作) 查找使该操作符任何一边的检测条件为真的匹配文件。有时候直接缩写成 -o
-not (非操作) 查找使该操作符后面的检测条件为假的匹配文件。有时候直接缩写成-!
() (括号操作) 有时为了获得想要的结果必须扰乱默认的执行顺序,即便不需要,将一串字符表达式括起来对提高命令的可读性也很有帮助。请注意,括号字符在 shell 环境中有特殊意义,所以必须将它们在命令行中用引号引起来,这样才能作为 find 的参数传递。通常用反斜杠来避免这样的问题
  1. 操作符的补充说明
  • 对照这张逻辑操作符的列表,重新剖析上述的 find 命令行。从最全局的角度看,所有检测条件由一个或 (or) 操作符分成了两组。
    1
    (表达式1) -or (表达式2) 
  • 这样做是有原因的,因为我们查找的是具有某种权限设置的文件和另外一种权限设置的目录。
  • 那既然要同时查找文件和目录,怎么不使用 and 而是用 or ?因为 find 命令在扫描所有的文件和目录时,会判断每一个文件或目录是否匹配该 test 项检测条件。
  • 而我们的目标是具有不安全访问权限的文件或是具有不安全访问权限的目录,都知道匹配者不可能既是文件又是目录,所以不能用 and 逻辑关系。由此,可将表达式扩充如下:
    1
    (file with bad perms) -or (directory with bad perms)
  • 接下来的问题就是如何判断文件或是目录具有危险权限,我们该怎么做呢?事实上,我们并不需要直接去寻找危险权限的文件或是目录,而是查找那些具有不好权限的文件或目录,因为我们知道什么是好的权限。
  • 对于文件来说,权限是 0600 表示好的,而对于目录,好的权限应该是 0700 。于是,判断具有不好访问权限的文件的表达式如下:
    1
    -type f -and -not -perms 0600
  • 同样,判断具有不好访问权限的目录的表达式如下:
    1
    -type d -and -not -perms 0700
  • and 操作是默认的,所以可以安全地移除。把如上表达式都整理到一起一下。
    1
    find ~ (-type f -not -perms 0600) -or (-type d not -perms 0700)
  • 然而,由于括号在 shell 环境下有特殊含义,所以我们必须对它们进行转义以防 shell 试图编译它们。在每个括号前加上反斜杠便可解决此问题。
  • 逻辑运算符的另外一个特性也很值得大家了解,有如下两个逻辑操作符分开的表达式:
    1
    expr1 -operator expr2
  • 在任何情况下,表达式 expr1 都会被执行,而中间的操作符将决定表达式 expr2 是否被执行
  1. find 命令的 and/or 逻辑运算
表达式 expr1 的结果 逻辑操作 表达式 expr2 执行情况
and 总是执行
and 不执行
or 不执行
or 总是执行
  1. 操作符细节说明
  • 主要还是为了提高效率,以 -and 逻辑运算为例,很明显表达式 expr1 -and expr2 的值在 expr1 为假的情况下不可能为真,所以就没有必要再运算表达式 expr2 了。
  • 同样,对于表达式 expr1 -or expr2 ,在表达式 expr1 为真的情况下其逻辑值显然为真,于是就没有必要在运算表达式 expr2 了。

action 选项

基本介绍

  1. 前面 find 命令已经查找到所需要的文件,但是我们真正想做的是处理这些已查找的文件。幸运的是,find 命令允许直接对搜索结果执行动作。

预定义动作

  1. 基本介绍
  • 对搜索到的文件进行操作,即可以用诸多现成的预定义动作指令,也可以使用用户自定义的动作。首先来看一些预定义动作。
  1. 预定义的 find 命令操作
动作 功能描述
-delete 删除匹配文件
-ls 对匹配文件执行 ls 操作,以标准格式输出其文件名以及所要求的的其他信息
-print 将匹配的文件的全路径以标准形式输出,当没有指定任何具体操作时,该操作是默认操作
-quit 一旦匹配成功便退出
  1. 案例
  • 本章开头所举的第一个例子如下。
    1
    find ~
  • 此命令行产生了一个包含当前系统主 ~ 目录中所有文件和子目录的列表。
  • 列表之所以会在屏幕上显示出来,是因为在没有指定其他操作的情况下,-print 操作是默认的。因此,上述命令行等效于如下形式的命令行。
    1
    find ~ -print
  • 当然也可以使用 find 命令删除满足特定条件的文件。示例如下,此命令行用于删除 .BAK (这种文件一般是用来制动备份文件的) 后缀的文件。
    1
    find ~ -type f -name '.BAK' -delete
  • 本例中,用户主目录及其子目录下的每个文件都搜索了一遍匹配文件名以 .BAK 结尾的文件。一旦被找到,直接删除。
  • 毫无疑问,在使用 -delete 操作时,你一定要格外小心。最好先用 -print 操作确认搜索结果后再执行 -delete 删除命令。
  • 补充介绍一下逻辑运算是如何映像 find 的 action 操作的。
    1
    find ~ -type f -name '*.BAK' -print
  • 正如我们所知道,该命令行用来查找所有文件名以 .BAK 结尾的普通文件 (-type f) 并且以标准形式 (-print) 输出每个匹配文件的相关路径名。
  • 然而,该命令行之所以照这样方式是由每个 test 选项和 action 之间的逻辑关系决定的。记住,每个 test 选项和 action 选项之间默认的逻辑关系是与 (and) 逻辑。下面的命令行逻辑关系能看得更清楚些。
    1
    find ~ -type f -and -name '*.BAK' -and -print
  • 如上便是完整的表达式,列出了逻辑运算符是如何影响 action 操作的。
  1. 逻辑运算符的影响
检测条件/执行操作 在该情况下执行操作
-print -type f 和 -name ‘*.BAK’ 条件都匹配时,也就是文件是普通文件并且文件名也是以 .BAK 结尾时
-name ‘*.BAK’ -type f 项匹配,也就是说文件是普通文件时
-type f 总是执行,因为是与逻辑关系中的第一个操作数
  1. 补充说明
  • test 选项与 action 选项之间的逻辑关系决定了他们的执行情况,所以 test 选项和 action 选项的顺序很重要。
  • 例如,如果重新排列这些 test 选项和 action 选项,并将 -print 操作作为逻辑运算的第一个操作数,那么命令行的运行结果将会有很大不同。
    1
    find ~ -print -and -type f -and -name '*.BAK'
  1. 此命令行把每个文件显示出来 (因为 -print 操作运算值总是为真) ,然后再对文件类型以及特定的文件扩展名进行匹配检查。

用户自定义操作

  1. 除了已有的预定义操作命令,同样也可以任意调用用户想要执行的操作命令,传统的方法就是像以下命令行使用 -exec 操作。
    1
    -exec command {};
  2. 该格式中的 command 表示要执行的操作命令名,{} 花括号代表的是当前路径,而分号作为必需的分隔符表示命令结束。使用 -exec 完成 -delete 操作示例如下。
    1
    -exec rm '{}' ';'
  3. 同样,由于括号和分号字符在 shell 环境下有特殊含义,所以在输入命令行时,要将它们用引号引起来或者转义符隔开。
  4. 当然,交互式地执行用户自定义操作也不是不可能。通过使用 -ok 操作取代原来的 -exec 操作,每一次指定命令执行之前系统都会询问用户。
    1
    2
    3
    4
    5
    find ~ -type f -name 'foo*' -ok ls -l '{}' ';'
    < ls ... /home/me/binfoo > ? y
    -rwxr-xr-x 1 me me 224 2011-10-29 18:44 /home/me/foo
    < ls ... /home/me/foo.txt > ? y
    -rw-r--r-- 1 me me 0 2012-09-19 12:53 /home/me/foo.txt
  5. 上例中,查找文件名以 foo 字符串开始的文件,并且每次找到匹配文件后执行 ls -l 命令。 -ok 操作会在 ls 命令执行之前询问用户是否执行。

提高效率

  1. 当使用 -exec 操作时,每次查找到匹配文件后都会调用执行一次指定命令。但有时用户更希望只调用一次命令就完成所有匹配文件的操作。例如,多数人可能更喜欢这样的命令执行方式。
    1
    2
    ls -l file1
    ls -l file2
  2. 而不是以下这样的方式。
    1
    ls -l file1 file2
  3. 第一种方式只需要执行命令一次而第二种方式则要多次重复执行。实现这样的一次操作的两种方法:一种方式比较传统,使用外部命令 xarges ;另一种则是使用 find 本身自带的新特性。首先介绍下第二方法。
  4. 通过将命令行末尾的分号改为加号,便可将 find 命令所搜索到匹配结果作为指定命令的输入,从而一次完全对所有文件操作。
    1
    find ~ -type f -name 'foo*' -exec ls '{}' +
  5. 上面的例子,每次找到匹配文件后就执行一次 ls 命令。将上述命令行改成下面的命令行。
    1
    2
    3
    find ~ -type f -name 'foo*' -exec ls -l '{}' +
    -rwxr-xr-x 1 me me 224 2011-10-29 18:44 /home/me/bin/foo
    -rw-r--r-- 1 me me 0 2012-09-19 12:53 /home/me/foo.txt
  6. 我们也能得到相同的结果,但是系统整体只执行一次 ls 命令。
  7. 同样我们可以使用 xargs 命令获得相同的效果,xargs 输入, xargs 反过来将其转换成了 ls 命令的输入参数列表,最后执行 ls 操作。
  8. 虽然一个命令行中可允许输入的参数有很多,但这并不表示可以无限输入,也存在命令行过长而使得 shell 编辑器无法承受的情况。
  9. 如果命令行中包含的输入参数太多而超过了系统支持的最大长度, xargs 只会尽可能对最大数量的参数执行指定操作,并不断重复这一过程知道所有标准输入全部处理完毕。
  10. 在 xargs 命令后面添加 –show-limits 选项,即可知道命令行最大能承受的参数数量。

返回到 playground 文件夹

  1. 现在可以实际应用 find 命令了。首先让我们创建一个包含很多子目录及文件的 playground 文件夹平台。
    1
    2
    [me@linuxbox ~]$ mkdir -p playground/dir-{00{1..9},0{10..99},100}
    [me@linuxbox ~]$ touch playground/dir-{00{1..9},0{10..9},100}/file-{A..Z}
  2. 不得不惊叹于 Linux 命令行的威力!简单的两行命令,就创建了一个包含 100 个子目录的 playground 文件夹,并且每个子目录中又包含 26 个空文件。
  3. 我们用来创造这个奇迹的方法包含一个熟悉的命令 mkdir、一个奇异的 shell 花括号扩展以及一个新命令 touch 。
  4. mkdir 命令结合 -p 选项 (-p 选项是的 mkdir 命令按指定的路径创建父目录) 的同时用花括号扩展,便完成了 100 个目录的创建。
  5. touch 命令一般用于设定或是更新文件的修改时间。然而,当文件名参数是一个不存在的文件时,那么该命令就会创建一个空文件。
  6. playground 文件夹里,总共创建了 100 个叫做 file-A 的文件。现在,我们可以查找它们。
    1
    [me@linuxbox ~]$ find playground -type f -name 'file-A'
  7. 请注意,与 ls 命令不同,find 命令不会产生有排列顺序的结果,其输出顺序是由在存储设备中的布局决定的。
  8. 下面的命令行验证了该文件夹确实有 100 个 file-A 文件。
    1
    2
    [me@linuxbox ~]$ find playground -type f -name 'file-A' | wc -l
    100
  9. 下面来看一个根据文件的修改时间查找文件的例子,在这创建备份文件以及按时间顺序排列文件时非常有用。首先需要创建一个用作比较修改时间的参照文件。
    1
    [me@linuxbox ~]$ touch playground/timestamp
  10. 该命令行创建了一个名为 timestamp 的空文件,并将当前时刻设为该文件的修改时间。我们可以使用另外一个便捷的命令 stat 来检验执行效果,stat 命令可以说是 ls 的增强版,该命令会将系统所掌握文件的所有信息及属性全部显示出来。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    [me@linuxbox ~]$ stat playground/timestamp
    File:'playground、timestamp'
    Size: 0 Blocks: 0 IO Block: 4096 regular empty file
    Device: 803h/2051d Inode: 14265061 Links: 1
    Access: (0644/-rw-r--r--) Uid: (1001/me) Gid:(1001/me)
    Acess: (0644/-rw-r--) Uid: (100/me) Gid:(1001/me)
    Access:2012-10-08 15:15:39.00000000 -0400
    Modify: 2012-10-08 15:15:39.00000000 -0400
    Change: 2012-10-08 15:15:39.00000000 -0400
  11. 接下来,我们便可以用 find 命令更新 playground 文件夹里的一些文件。
    1
    [me@linuxbox ~]$ find playground -type f -newer playground/timestamp
  12. 命令行的运行结果包含 100 个文件名为 file-B 的文件。由于我们是在对 timestamp 文件执行了 touch 命令之后,才对 playground 文件夹对名为 file-B 的所有文件执行了 touch 操作,所以它们现在要比 timestamp 文件新,从而我们可选用 -newer test 选项查找。
  13. 最后,回顾之前讨论的查找不安全访问权限文件的例子,并将其用于 playground 目录。
    1
    [me@linuxbox ~]$ find playground \(-type f -not -perm 0600 \) -or \(-type d -not -perm 0700 \)
  14. 该命令行列出了 playground 目录下的下的所有的 100 个子目录以及 2600 个文件 (再加上 timestamp 文件和 playground 自身,总共 2072 个) ,之所以全部列出是因为 playground 里面没有一个文件满足安全访问权限的要求。
  15. 运用前面所学 find 命令的 operator 选项和 action 选项的知识,我们可以在命令后面增加 action 参数选项来改变 playground 目录下文件或目录的访问权限。
    1
    [me@linuxbox ~]$ find playground \( -type f -not -perm 0700 -exec chmod 0700 '{}' ';' \)
  16. 依据日常经验,大家可能会觉得用两条命令-分别针对目录和文件,比用这样一个长而复杂的命令容易的多。不过知道这一知识点总比不知道好,此处的重点是,要了解如何结合使用操作符选项与行为选项,来执行一些有用的任务。

option 选项

  1. 基本介绍
  • 最后,我们谈一个 find 命令的 option 选项。 option 选项用于控制 find 命令的搜索范围。
  • 在构成 find 命令的表达式时,他们可能包含在其他测试选项或行为选项之中。
  1. find 命令的 option 选项
选项 描述
-depth 引导 find 程序处理目录前先处理目录内文件。当指定 -delete 操作时,该参数选项会自动调用
-maxdepth levels 当执行测试条件行为时,设置 find 程序陷入目录数的最大级别数
-mindepth levels 在应用测试条件和行为之前,设置 find 程序陷入目录数的最小级别数
-mount 引导 find 不去遍历挂载在其他文件系统上的目录
-noleaf 指导 find 程序不要基于正在搜索 UNIX 文件系统的假设来优化它的搜索。当扫描 DOS/Windows 文件系统和 CD 时,会用到该选项

参考文章

  • 转载:Linux 命令行大全