第17章:搜索文件
当我们徘徊在 Linux 系统时,有一件事变得非常清楚:一个典型的 Linux 系统有一大堆文件!这引出了一个问题,「我们怎么才能找到东西?」我们已经知道 Linux 文件系统是依初代类 Unix 系统传下来的惯例组织的,但是绝对数量的文件可能带来令人生畏的问题。
本章中,我们将会看到两个用来查找系统中文件的工具。
locate
用名称找文件find
在目录等级中搜索文件
我们还将看到一个处理文件结果列表的文件搜索命令。
xargs
从标准输入建立和执行命令行
另外,还将介绍一对辅助我们浏览的命令。
touch
改变文件的时间stat
显示文件或文件系统的状态
locate - 查找文件的简易方法
locate
程序执行快速路径名数据库的搜索,输出每一个匹配给定字符串的名称。例如,我们想找到所有文件名以 zip
开头的程序。由于我们要找的是程序,可以假定包含程序的目录会以 bin/
结尾。因此,我们可以尝试这样用 locate
找到所需的文件:
locate
会搜索其路径数据库,输出任何包含字符串 bin/zip
的条目。
如果搜索的需求不是这么简单,我们可以将 locate
和其它工具如 grep
合并使用,来设计出更有趣的搜索。
locate
程序已经有多年的历史了,并有几种常用的变体。Linux 发行版中最常用的两个是 slocate
和 mlocate
,不过它们通常是由名为 locate
的符号链接而得以访问。不同版本的 locate
有着重叠的选项集。有些版本包含正则表达式匹配(我们将在第 19 章学到)并支持通配符。请查阅 locate
的手册页来确定已安装的 locate
是哪个版本。
locate 的数据库从哪里来的?
你可能注意到,在有些发行版中,
locate
在刚安装完系统后不能工作,不过第二天当你再尝试的时候,它就能正常工作了。是什么赋予了它工作的能力?locate
数据库有另一个名为updatedb
的程序所创建。它通常由一个计划任务(cron job)周期性地运行,计划任务是指由cron
守护程序定期执行的一个任务。大多数系统每天运行一次updatedb
以支持locate
程序。由于数据库不是持续更新的,你会注意到locate
不会显示近期的文件。要克服这点,可以用超级用户权限在命令行中手动执行updatedb
程序。
find - 查找文件的复杂方法
locate
程序可以单独基于文件名查找文件,find
程序基于各种属性搜索一个给定目录(及其子目录)中的文件。我们将会花费大量时间学习 find
,因为它有大量有趣的特性,在后续章节中学习程序概念时会使得我们一再地查看。
最简单的用法是,给定一个或多个目录名,用 find
搜索。例如,要产生一份家目录的文件列表,我们会这么用它:
在大多数活跃的用户帐户中,该命令会产生一份很长的列表。由于列表是被发送到标准输出的,我们可以将列表管道输出给其它程序。来用 wc
统计一下文件的数目。
哇,我们一直真忙!find
的优点在于它可用于识别符合特定条件的文件。它通过(有点奇怪)应用选项(options)、测试(tests)和行为(actions)来实现。首先来看测试。
测试
假设我们想要从搜索中得到一份目录的列表。要做到这点,可以加下列测试:
添加 -type d
限制仅搜索目录。相反,我们可以限制仅搜索常规文件:
表 17-1 列出了 find
测试所支持的常用的文件类型。
表 17-1:find 文件类型
文件类型
描述
b
块特殊设备文件
c
字符特殊设备文件
d
目录
f
常规文件
l
符号链接
我们还可以通过文件大小和文件名,添加一些附加的测试来搜索。来搜索全部常规文件,要求匹配通配符 *.JPG
并大于一兆字节。
在本例中,加了 -name
测试紧跟其后的通配符式样。注意是如何加引号以防止路径名扩展。随后,加了 -size
测试随后的字符串 +1M
。为首的 +
指示我们正在查找的文件大于指定数字。一个为首的 -
则表明小于指定数字。如无指定符号,则「精确匹配数字」。末尾 M
指示度量单位为兆字节。表 17-2 列出了可用于指定单位的字符。
表 17-2: find 尺寸单位
字符
单位
b
512 字节块。缺省计量单位。
c
字节数。
w
两字节词。
k
千字节(1024 字节单位)。
M
兆字节(1048576 字节单位)。
G
吉字节(1073741824 字节单位)。
find
支持大量的测试。表 17-3 提供了常见的概述。请注意,在需要数字参数的情况下,可以应用上面讨论的相同的 +
和 -
符号。
表 17-3:find 测试
测试
描述
-cmin
n
匹配那些在 n 分钟内修改过内容或属性的文件或目录,-n
指定少于 n 分钟,而 +n
指定多于 n 分钟。
-cnewer
file
匹配那些内容或属性修改时间比 file 文件的更近的文件或目录。
-ctime
n
匹配那些内容或属性最后修改时间在 n * 24 小时内的文件或目录。
-empty
匹配空文件和空目录。
-group
groupname
匹配那些属于 groupname 属组的文件或目录。groupname 可以是组名称或者是数字形式的组 ID。
-iname
pattern
和 -name
测试一样,只是不区分字符大小写。
-inum
n
匹配 inode 编号为 n 的文件。对于查找所有链接到特定 inode 的硬链接。
-mmin
n
匹配那些最近 n 分钟内修改过内容的文件或目录。
-mtime
n
匹配那些最近 n * 24 小时内修改过内容的文件或目录。
-name
pattern
匹配通配符 pattern 的文件或目录。
-newer
file
匹配那些内容修改时间比 file 文件的更近的文件或目录。在写脚本执行文件备份时很有用。每次你创建一个备份,更新一个文件(如日志),然后用 find
确定从上次更新之后哪些文件变动过。
-nouser
匹配那些不属于任一合法用户的文件或目录。这可以用来查找那些属于已被删除帐号的文件,或者检测攻击者的活动。
-nogroup
匹配那些不属于任一合法属组的文件或目录。
-perm
mode
匹配那些权限设置等于指定 mode 的文件或目录。mode 可以是八进制或符号表示法。
-samefile
name
与 -inum
测试类似。匹配共享同一 name 的文件。
-size
n
匹配尺寸等于 n 的文件。
-type
c
匹配类型等于 c 的文件。
-user
name
匹配文件属主为 name 的文件或目录。用户可以是由用户名或数字用户 ID 表示。
以上不是一个完整的清单。find
手册页有完整的详述。
操作符
即便用上了所有 find
所提供的测试,我们还是需要一个更好的方法来描述测试之间的逻辑关系(logical relationships)。例如,如果我们需要确定目录中的所有文件和子目录是否具有安全权限,该怎么办?我们会查找那些权限不是 0600
的文件和权限不是 0700
的目录。幸运的是,find
提供了一个用逻辑操作符(logical operators)的组合测试来创建更复杂的逻辑关系。要表达上述的测试,我们可以这样做:
令人惊讶!看起来是这么古怪。都是些什么东西?实际上,一旦你对操作符有所了解,也就不觉得有多复杂了。表 17-4 描述了用在 find
上的逻辑操作符。
表 17-4:find
逻辑操作符
操作符
描述
-and
仅匹配操作符两边结果都为真的测试。可以缩写为 -a
。注意当没有提供操作符时,默认隐含了 -and
。
-or
匹配操作符任一边结果为真的测试。可以缩写为 -o
。
-not
匹配操作符后结果为假的测试。可以缩略为一个感叹号(!
)。
()
将测试和操作符组合在一起,组成更大的表达式。用来控制逻辑评估的先后顺序。默认地,find
按从左到右的顺序评估。而越过默认评估顺序以获得想要的结果,也是经常必要的。即便不需要,有时包含组合字符也能够增进命令的可读性。注意,由于括号在 shell 中有其特殊意义,所以当使用时必须被引用以允许它们作为 find
的参数。通常会用反斜杠符号来转义。
有了这份操作符列表,就可以试着解构 find
命令了。当从最上层开始看的时候,可以看到测试是由被 -or
操作符分隔成的两组组成的。
这是有理的,因为我们正在检索具有某种权限的文件和不同属性的目录。我们正在找的是文件和目录,为什么要用 -or
而不是 -and
?当 find
扫描文件和目录时,会评测每个文件和目录来看它是否匹配指定的测试。我们想要知道它是有着错误权限的文件还是一个有着错误权限的目录。它不可能同时都是。所以当我们扩展一下组合的表达式,就可以看到:
接下来的挑战是如何测试「错误的权限」。该怎么做呢?实际上,我们没有做。我们要测试的是「不正确的权限」是因为我们知道什么是「正确的权限」。对于文件,我们定义 0600
是正确的;对于目录,我们定义 0700
是正确的。下面这个表达式将测试具有「不正确」权限的文件:
而下面的是测试目录的:
如表 17-4 所述,-and
操作符由于是默认隐含的,所以可以被安全地移除。最后,我们把这些都组合在一起,得到最终的命令就是:
还有,因为括号在 shell 中有特殊含义,我们必须将其转义,以预防 shell 试图解释它们。就需要在每个括号前放一个反斜杠符号来完成转义。
关于逻辑操作符,还有一个很重要的特性需要理解。假设有两个表达式,被一个逻辑操作符分隔:
在任何情况下,expr1
总是会被执行,然而,操作符将会决定 expr2
是否会被执行。表 17-5 描述了其如何工作。
表 17-5:find
and/or
逻辑
expr1
的结果
操作符
expr2
是……
True
-and
总是被执行
False
-and
从不被执行
True
-or
从不被执行
False
-or
总是被执行
为何会这样?这样做是为了提高性能。拿 -and
为例,我们知道 expr1 -and expr2
的值在 expr1
的结果为假的时候,是不可能为真的,所以就没有必要去执行 expr2
了。同样的,如果我们有个 expr1 -or expr2
这个表达式,当 expr1
的结果为真时,就没有必要计算 expr2
了,因为已经知道这个表达式的结果是真了。
好了,这样有助于更快地运行。为何这很重要?因为我们可以依赖这个行为来控制操作的执行方式,我们很快就会看到。
预定义的行为
让我们来完成一些工作!从 find
命令获取一个结果列表是有用的,但我们真正想做的是操作列表上的项目。幸运的是,find
允许基于搜索结果来执行操作。这里有一组预定义的行为和几种途径来应用用户定义的行为。首先,我们看表 17-6 中所列的几个预定义行为。
表 17-6:预定义 find
行为
行为
描述
-delete
删除当前匹配的文件。
-ls
对匹配的文件执行等同于 ls -dils
的命令。将结果输出到标准输出。
-print
对匹配的文件输出其完整的路径名。如果没有指定别的行为,这就是其默认行为。
-quit
当匹配后退出。
伴随着测试,有着更多的操作。可以查看 find
的手册页获取完整细节内容。
在第一个例子中,我们做这个:
这条命令会制造一份清单,包含我们家目录中的所有文件和子目录。之所以会产生一份清单,是因为若没有指定其它行为,则隐含了 -print
行为。所以,我们的命令也可以被表述为下面这个:
我们可以用 find
来删除符合某一标准的文件。例如,要删除文件扩展名为 .bak
的文件(经常被用来指定备份文件),可以使用这条命令:
此例中,会检索所有在用户家目录中的(包括其子目录中的)文件名以 .bak
结尾的文件。一旦找到,就删除。
警告:不言而喻,当使用
-delete
行为时,用户应当极其谨慎。总是应当先用-delete
行为,以确认检索结果。
在继续展开之前,让我们再看一下逻辑操作符怎样影响行为。考虑下面的命令:
如我们所见,这条命令将寻找每个常规文件(-type f
),其文件名以 .bak
结束(-name '*.bak'
),并且会输出其每个匹配文件的相对路径到标准输出(-print
)。然而,命令如此执行的原因是取决于每个测试和行为之间的逻辑关系的。记住,默认情况下,每个测试和行为之间都隐含了一个 -and
关系。我们可以将命令作如下表述,以便更容易地看清逻辑关系:
在完整表达了命令之后,来看下逻辑操作符如何影响其执行:
测试/行为
如果…则执行
-print
-type f
和 -name '*.bak'
是真
-name '*.bak'
-type f
是真
-type f
总是会被执行,因为在 -and
关系中,这是第一个测试或行为。
由于测试和行为之间的逻辑关系决定了它们中的哪些能被执行,所以我们可以看到测试和行为的顺序是很重要的。举例而言,如果我们将测试和行为重新排序,使得 -print
行为在首位,则命令的行为将会有很大的不同。
这一版本的命令会打印每个文件(-print
行为总是评估为真),然后测试是否是文件类型和指定文件扩展名。
用户定义的行为
除了预定义行为,我们还可以调用任意命令。传统的做法是使用 -exec
行为。该行为工作方式:
这里,command
是命令名,{}
是当前路径名的符号表示,分号(;
)是指示命令结束的分隔符。这里有一个例子,用 -exec
来执行之前所讨论过的 -delete
行为:
重申一下,由于括号和分号字符在 shell 中有其特殊意义,所以必须用引号或者转义。
还可以交互式地执行用户定义行为。用 -ok
行为替换 -exec
,在执行每个指定命令之前,用户都会得到提示。
在这个例子中,我们检索文件名以 foo
开头的文件,每当找到一个,就执行 ls -l
命令。使用 -ok
行为,会在执行 ls
命令之前提示用户。
增进效率
当使用 -exec
行为时,每找到一个匹配文件,就运行一次指定命令的新实例。或许我们可以合并所有的检索结果,运行一次命令的实例。例如,与其这样运行命令:
我们更喜欢这样运行:
这导致命令只执行一次而不是多次。有两种方法:传统的做法,使用外部命令 xargs
,替代方法是,使用 find
自身的新特性。我们先讨论替代方法。
通过将句尾的分号改成加号,我们激活了 find
的能力,使其合并检索结果为一个命令的参数列表。回到我们的例子中,下例中每次找到匹配文件就会执行一次 ls
:
变更后,如下:
我们得到了相同的结果,但是系统则仅执行了一次 ls
命令。
xargs
xargs
命令执行一个有趣的功能。它从标准输入接收输入,将其转换为一个指定命令的参数列表。在我们的例子中,会这样使用它:
这里我们看到 find
命令的输出被管道输入到 xargs
,反过来,为 ls
命令构建一个参数列表,然后执行。
注意:可以被置入命令行的参数数量是非常巨大的,没有任何限制。所以可能会制造一个太长的命令,以至于 shell 不能接受。当一条命令行超出了系统支持的最大长度时,
xargs
以最大长度的参数执行指定命令,然后重复该进程,直到穷竭标准输出。要查看命令行的最大长度,以--show-limits
为参数执行xargs
。处理有趣的文件名
类 Unix 系统允许在文件名中存在空格(甚至是换行符!)。这会使得像
xargs
这样的程序在给其它程序构建参数列表的时候出现问题。内嵌的空格会被当作一个分隔符,导致命令将每个被空格分隔的词语作为一个参数。要克服这点,find
和xargs
允许用null
字符作为参数分隔。一个null
字符在 ASCII 中被定义为数字 0(相反的,例如空格字符,则在 ASCII 中被定义为数字 32)。find
命令提供一个行为-print0
,以产生null
分隔符的输出,而xargs
命令则有--null
(或-0
)选项,以接受null
分隔的输入。这个有个例子:使用该技术,我们可以保证所有文件,甚至那些文件名中内嵌有空格的文件都能得到正确地处理。
回到游戏场
是时候将 find
置于一些(几乎)实际用途中了。来建立一个游戏场,并试验一些我们已经学到的知识。
首先,建立一个游戏场,并建立许多子目录和文件。
命令行的神奇威力!用这两条命令,我们创建了 playground
目录,内含 100 个子目录,每个子目录包含 26 个空文件。请用图形界面试试!
我们完成这个魔法所采用的方法,涉及一个熟悉的命令(mkdir
)和一个奇特的 shell 扩展(大括号),还有一个新命令,touch
。当 mkdir
和 -p
选项(使得 mkdir
可以创建指定路径的上级目录)以及大括号扩展组合在一起的时候,我们就能创建 100 个子目录了。
touch
目录通常用来设置或更新访问、变更、修改文件的时间。然而,如果一个文件名参数是一个不存在的文件时,会创建一个空文件。
在我们的游戏场中,我们创建了 100 个名为 file-A
的文件实例。让我们找到它们。
注意,不同于 ls
,find
不会产生一个排序好的结果。其顺序取决于存储设备的布局。我们可以通过这个方法确认实际上有 100 个文件实例:
接下来,基于修改时间来看一下找到的文件。这有助于创建备份文件或者按时间顺序组织文件。首先,将创建一个参考文件,以比较修改时间。
上述命令创建了一个名为 timestamp
的空文件,并将其修改时间设置为当前时间。我们可以用另一个简便的命令 stat
来验证,它是一种不同版本的 ls
。stat
命令显示系统内容,理解文件及其属性。
若再次运行 touch
并用 stat
检查,会看到该文件的时间已经更新了。
接下来,用 find
更新一些游戏场文件。
这会更新所有游戏场里名为 file-B
的文件。然后,我们通过与参考文件 timestamp
比较,用 find
识别处更新过的文件。
结果包含了 100 个 file-B
的实例。因为我们在更新了 timestamp
文件之后才执行 touch
更新了游戏场里所有名为 file-B
的文件,它们现在就比 timestamp
文件更新,因此能被 -newer
测试所识别。
最后,让我们回到之前执行过的错误权限的测试,将其应用到游戏场中。
该命令列出游戏场中所有 100 个目录和 2,600 个文件(还包括 timestamp
文件和 playground
目录自身,共计 2,702 项),因为没有一个是符合我们「正确权限」的定义。用我们所学的操作符和行为的知识,可以加一些行为到命令行中,以便将新的权限应用到游戏场中的文件和目录。
在日常操作中,我们或许会觉得写两条命令会比写这样一个大型组合命令更简单,一条给目录,另一条给文件,但是这个组合命令很好地让我们知道,我们可以通过这个方式实现。这里的重点是理解如何一起使用操作符和行为,以执行有用的任务。
选项
最后,来看选项。选项用来控制 find
的搜索范围。在构建 find
表达式时,它们可以被包含在其它测试和行为中。表 17-7 列出了最常用的 find
选项。
表 17-7:find
选项
选项
描述
-depth
指导 find
先处理目录中的文件,再处理目录自身。当指定 -delete
行为时,会自动应用该选项。
-maxdepth levels
当执行测试和行为时,设置 find
下探到目录树中的最大层次数。
-mindepth levels
当执行测试和行为时,设置 find
下探到目录树中的最小层次数。
-mount
指导 find
不要遍历加载在别的文件系统上的目录。
-noleaf
指导 find
不要基于类 Unix 文件系统的假设来优化其检索。当扫描 DOS/Windows 和 CD-ROM 文件系统时,需要该选项。
总结
易于发现,locate
较简单,而 find
较复杂。它们各有其用途。花点时间来探索 find
的诸多特性。经常使用,可以增进你对 Linux 文件系统操作的理解。
扩展阅读
Last updated
Was this helpful?