Linux命令——查找文件

查找文件

1、locate---简单的文件查找方法

locate命令对路径名执行快速的数据库查找,然后输出与给定字符串匹配的各个名称。例如,我们想查找名称以zip开头的所有程序。由于查找的是程序文件,因此可以假定包含查找程序文件的目录名应以bin/结尾。尝试按照下面的方法使用locate命令:

bash 复制代码
[me@linuxbox ~]$ locate bin/zip

locate命令会查找其路径名数据库,输出所有包含字符串bin/zip的匹配项:

bash 复制代码
/usr/bin/zip
/usr/bin/zipcloak
/usr/bin/zipgrep
/usr/bin/zipinfo/usr/bin/zipnote
/usr/bin/zipsplit

如果查找条件比较复杂,可以将locate命令与grep等其他命令结合起来,设计出更有趣的查找方法:

bash 复制代码
[me@linuxbox ~]$ locate zip | grep bin
/bin/bunzip2
/bin/bzip2
/bin/bzip2recover
/bin/gunzip
/bin/gzip
/usr/bin/funzip
/usr/bin/gpg-zip
/usr/bin/preunzip
/usr/bin/prezip
/usr/bin/prezip-bin
/usr/bin/unzip
/usr/bin/unzipsfx
/usr/bin/zip
/usr/bin/zipcloak
/usr/bin/zipgrep
/usr/bin/zipinfo
/usr/bin/zipnote
/usr/bin/zipsplit

locate命令已经存在了很长一段时间了,也有几种常用的变体。slocate命令和mlocate命令是现代Linux发行版中常见的两种,不过两者通常是通过符号链接locate命令访问的。不同版本的locate有一些重叠的选项。有些版本支持正则表达式(我们会在第19章讲到)和通配符。查看locate命令的手册页,确定系统安装的是哪个版本的locate命令。

在一些Linux发行版中,你可能会注意到locate命令在系统安装好之后是无法工作的,但如果你过一天再尝试,它又正常了。这是怎么回事?locate命令的数据库是通过另一个命令updatedb创建的。它通常作为cron作业,由守护进程cron定期执行。大多数包含locate命令的系统每天执行一次updatedb命令。因为数据库并不是持续更新的,所以你会注意到在使用locate命令时,新创建的文件不会出现在查找结果中。要想解决这个问题,可以切换成超级用户,手动执行updatedb命令。

2、find---复杂的文件查找方法

locate命令仅根据文件名查找文件,而find命令可以根据各种属性在指定目录(及其子目录)中查找文件。我们将会花大量的篇幅介绍find命令的用法,因为它有很多值得关注的特性,并会在后文介绍编程概念时多次讲到。

find命令较简单的用法就是为其指定一个或多个目录作为查找范围。例如,要想生成主目录的文件列表,可以这样:

bash 复制代码
[me@linuxbox ~]$ find ~

对于大多数活跃用户,该命令会产生一个很长的文件列表。因为列表被发送到标准输出,所以我们可以通过管道将其导入其他程序。来试一试用wc命令统计文件数量:

bash 复制代码
[me@linuxbox ~]$ find ~ | wc -l
47068

find命令的美妙之处在于能够找出符合特定条件的文件。这是通过运用各种选项(option)​、测试条件(test)以及操作(action)来实现的(看起来有点奇怪)​。我们先来看测试条件。

2.1、测试条件

假设我们想查找目录。为此,可以加入下列测试条件:

bash 复制代码
[me@linuxbox ~]$ find ~ -type d | wc -l
1695

加入测试条件-type d,限制只查找目录。相反,我们也可以使用下列测试条件,限制只查找普通文件:

bash 复制代码
[me@linuxbox ~]$ find ~ -type f | wc -l
38737

下表列出了find命令支持的常见文件类型。

还可以加入其他测试条件,按照文件大小和文件名查找。让我们来查找所有匹配通配符模式*.JPG且大于1MB的普通文件:

bash 复制代码
[me@linuxbox ~]$ find ~ -type f -name "*.JPG" -size +1M | wc -l
840

在这个例子中,我们在-name测试条件之后添加了通配符模式。注意,将通配符放入双引号中是为了避免Shell对其进行路径名扩展。接着在-size测试条件之后添加了+1M。前面的加号表示查找的文件大小比指定数值大;如果前面是减号,表示比指定数值小;没有符号则表示与指定值完全相等。末尾的M表示计量单位是MB。下表列出了用于指定计量单位的字符。

find命令支持大量测试条件。下表列出了常见测试条件。注意,如果需要指定数值参数,先前讨论过的+和-表示法同样适用。

2.2、操作符

即使有了find命令提供的测试条件,我们可能仍需要一种更好的方式来描述测试条件之间的逻辑关系。例如,如何确定某目录下所有的文件和子目录是否都设置了安全的访问权限?我们可以查找权限不为0600的文件和权限不为0700的目录。好在find命令提供了一种方式,可以使用逻辑操作符(简称操作符)组合多个测试条件,从而表达更为复杂的逻辑关系。之前的问题能够通过下列命令来解决:

bash 复制代码
[me@linuxbox ~]$ find ~\ ( -type f -not -perm 0600\) -or\ ( -type d -not -perm 0700\)

这看起来也太怪异了。这都是些什么东西?其实,只要你明确了操作符的来龙去脉,一切就豁然开朗了。下表列出了find命令的操作符。

让我们来拆解上面的find命令。从整体来看,测试条件分为两组,彼此之间由-or分隔:

bash 复制代码
( expression 1 ) -or ( expression 2 )

这样做是有原因的,因为我们查找的文件和目录各自具有不同的权限。既然要查找文件和目录,为什么不用-and,而是选择-or?因为find命令在扫描文件和目录时,会逐一判断其是否符合指定的测试条件,而我们想知道是否存在权限设置有误的文件或目录。这两种条件不可能同时满足。如果把分组表达式展开,就是下面这样:

bash 复制代码
( file with bad perms ) -or ( directory with bad perms )

接下来的挑战就是如何测试"有误的权限"​。该怎么分辨"正确的权限"和"错误的权限"呢?实际上,我们根本就不用分辨。因为我们知道什么是"正确的权限"​(good permissions)​,所以要测试的就是"不正确权限"​(not good permissions)​。对于文件,我们将正确权限定义为0600;对于目录,将正确权限定义为0700。那么,测试具有不正确权限的文件的表达式如下:

bash 复制代码
-type f -and -not -perms 0600

测试目录的表达式如下:

bash 复制代码
-type d -and -not -perms 0700

如表所示,因为-and是默认使用的,所以将其排除。将两个表达式组合在一起,就得到了最终的命令:

bash 复制代码
find ~ ( -type f -not -perms 0600 ) -or ( -type d -not -perms 0700 )

但是,因为括号对Shell具有特殊含义,为了避免Shell解释括号,必须对其进行转义。在每个括号前面加上反斜线即可。

操作符还有另一个重要的特性。假设有两个被操作符分隔的表达式:

bash 复制代码
expr1 -operator expr2

无论在哪种情况下,expr1都会被求值;但expr2是否会被求值,就得看操作符了。表中描述了其中的工作方式。

为什么要这样?为了改善性能。以-and为例。我们知道只要expr1为假,那么表达式expr1 -and expr2不可能为真,再对expr2求值就没必要了。与此类似,对于表达式expr1 -or expr2,如果expr1为真,就不用再求值expr2,因为我们已经知道该表达式一定为真。

好了,现在知道了这种做法能够提高速度。这很重要吗?是的,因为我们可以依靠这种行为来控制如何进行操作,你马上就会看到。

2.3、预定义操作

开始操作吧!得到find命令的查找结果固然有用,但我们真正想做的是对结果执行某些操作。幸运的是,find命令允许这么做。除了一些已经预定义好的操作,也可以应用用户的自定义操作。首先,让我们先来看一看下表列出的find命令的预定义操作。

和测试条件一样,预定义操作还有很多,详见find命令的手册页。

在第一个例子中,我们是这样做的:

bash 复制代码
find ~

该命令会产生主目录下包含的所有文件和子目录的列表。之所以如此,是因为如果没有指定别的操作,则默认执行-print操作。因此,这个命令也可以写成这样:

bash 复制代码
find ~ -print

我们可以使用find命令删除符合某些条件的文件。例如,下列命令能够删除扩展名为.bak(多用于表示备份文件)的文件:

bash 复制代码
find ~ -type f -name '*.bak' -delete

在这个例子中,查找用户主目录(及其子目录)中的所有文件,查找扩展名为.bak的文件。找到之后,将其删除。

在使用-delete操作时,一定要格外小心。在实际删除文件之前,先使用-print代替-delete,确认查找结果无误。

在继续往下进行之前,先来看一看操作符是如何影响操作的。执行下列命令:

bash 复制代码
find ~ -type f -name '*.bak' -print

该命令查找所有扩展名为.bak(-name '*.bak')的普通文件(-type f)​,将每个匹配文件的完整路径输出至标准输出(-print)​。但是,产生这种结果的原因是测试条件和操作之间的逻辑关系。记住,两者之间的默认关系是-and。我们也可以像下面这样更清晰地表达出这种关系:

bash 复制代码
find ~ -type f -and -name '*.bak' -and -print

以上就是完整的命令,下表列出了操作符是如何影响命令执行的。

因为测试条件与操作之间的逻辑关系决定了该由谁执行,因此两者的先后顺序就很重要了。例如,如果我们重新调整测试条件与操作的顺序,把-print操作放在最前面,命令结果可就大不一样了:

bash 复制代码
find ~ -print -and -type f -and -name '*.bak'

该命令会输出所有文件(-print操作总是为真)​,然后测试文件类型和指定的扩展名。

2.4、用户自定义操作

除了预定义操作,我们还可以针对查找结果调用任意命令。传统的实现方式是使用-exec操作。其用法如下:

bash 复制代码
-exec command {} ;

在这里,command是命令名。{}是代表当前路径名的符号。​;作为分隔符,表示命令结束。下面演示了使用-exec操作实现类似于先前讨论过的-delete操作的操作:

bash 复制代码
-exec rm '{}' ';'

因为{}和;对Shell具有特殊含义,所以必须对其进行标注或转义。

也可以交互式地执行用户自定义操作。只需要使用-ok操作代替-exec操作即可,在执行指定命令之前都会提示用户:

bash 复制代码
find ~ -type f -name 'foo*' -ok ls -l '{}' ';'
< ls ... /home/me/bin/foo > ? y
-rwxr-xr-x 1 me   me 224 2007-10-29 18:44 /home/me/bin/foo
< ls ... /home/me/foo.txt > ? y
-rw-r--r-- 1 me   me   0 2016-09-19 12:53 /home/me/foo.txt

在这个例子中,我们查找名称以foo开头的文件并对找到的每个文件执行命令ls -l。-ok操作会在执行ls命令之前提示用户。

2.5、提高效率

当使用-exec操作时,每找到一个匹配文件,就会执行指定命令的一个新实例。有时候,我们更愿意将所有的查找结果合并在一起,执行单个命令实例。例如,不要像下面这样执行命令:

bash 复制代码
ls -l file1
ls -l file2

而是这样:

bash 复制代码
ls -l file1 file2

这样一来,命令只需要执行一次,而不再执行多次。这有两种实现方法:传统方式和现代方式。前者使用外部命令xargs,后者使用find命令自身的一个新特性。我们先来说一说现代方式。

将结尾的分号改成加号,就能让find命令将查找结果组合成参数列表,供指定的命令一次性使用。在之前的例子中,每找到一个匹配文件,就要执行一次ls命令:

bash 复制代码
find ~ -type f -name 'foo*' -exec ls -l '{}' ';'
-rwxr-xr-x 1 me   me 224 2007-10-29 18:44 /home/me/bin/foo
-rw-r--r-- 1 me   me   0 2016-09-19 12:53 /home/me/foo.txt

将命令修改成下面这样:

bash 复制代码
find ~ -type f -name 'foo*' -exec ls -l '{}' +
-rwxr-xr-x 1 me   me 224 2007-10-29 18:44 /home/me/bin/foo
-rw-r--r-- 1 me   me   0 2016-09-19 12:53 /home/me/foo.txt

结果没变化,但系统只执行一次ls命令。

2.6、xargs

xargs命令的功能很有意思。它从标准输入接收输入,将其转换为指定命令的参数列表。我们可以在例子中这样使用:

bash 复制代码
find ~ -type f -name 'foo*' -print | xargs ls -l
-rwxr-xr-x 1 me   me 224 2007-10-29 18:44 /home/me/bin/foo
-rw-r--r-- 1 me   me   0 2016-09-19 12:53 /home/me/foo.txt

其中,find命令的输出结果通过管道传给了xargs命令,后者构造出ls命令的参数列表,然后执行该命令。

注意

命令参数的数量不是没有限制的。有可能出现命令长度超出Shell接受能力的情况。如果出现了这种情况,xargs命令可以使用系统支持的最大参数数量来执行指定的命令,然后重复此过程,直至处理完所有参数。在执行xargs命令时加入--show-limits选项就能知道系统支持的最大参数数量。

2.7、实战演练

我们要创建一个"练兵场"​,试一试刚才学到的技术。

首先,创建包含多个子目录和文件的playground目录:

bash 复制代码
[me@linuxbox ~]$ mkdir -p playground/dir-{001..100}
[me@linuxbox ~]$ touch playground/dir-{001..100}/file-{A..Z}

惊叹于命令行的威力吧!只用了两行,我们就创建了一个包含100个子目录(每个子目录包含26个空文件)的playground目录。你不妨在GUI中试一试!

实现这一神奇的效果的方法包括熟悉的mkdir命令、形式奇异的Shell扩展(花括号扩展)以及新命令touch。将mkdir命令及其-p选项(使mkdir命令创建指定路径的父目录)与花括号扩展组合在一起,我们成功地创建出了100个子目录。

touch命令通常用于设置或更新文件的访问(access)​、变更(change)​、修改(modify)时间。但如果文件名参数指定的文件并不存在,则创建同名的空文件。

playground目录中共创建了100个file-A文件。让我们将其找出来:

bash 复制代码
[me@linuxbox ~]$ find playground -type f -name 'file-A'

注意,和ls命令不同,find命令并不会对查找结果进行排序。具体的顺序是由存储设备的结构决定的。我们可以核实的确存在100个file-A文件:

bash 复制代码
[me@linuxbox ~]$ find playground -type f -name 'file-A' | wc -l
100

接下来,让我们来看如何根据文件的修改时间进行查找。这在创建备份文件或按时间顺序组织文件时能派上用场。首先需要创建一个用于比较修改时间的参照文件:

bash 复制代码
[me@linuxbox ~]$ touch playground/timestamp

该命令创建了一个名为timestamp的空文件并将其修改时间设置为当前时间。我们可以使用另一个方便的命令stat来验证这一点,该命令算是ls命令的增强版。stat命令能够显示出文件的所有信息及其属性:

bash 复制代码
[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)
Access: 2018-10-08 15:15:39.000000000 -0400
Modify: 2018-10-08 15:15:39.000000000 -0400
Change: 2018-10-08 15:15:39.000000000 -0400

如果再次使用touch命令,然后通过stat命令检查该文件,会发现文件的时间更新了:

bash 复制代码
[me@linuxbox ~]$ touch playground/timestamp
[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)
Access: 2018-10-08 15:23:33.000000000 -0400
Modify: 2018-10-08 15:23:33.000000000 -0400
Change: 2018-10-08 15:23:33.000000000 -0400

让我们使用find命令更新部分文件:

bash 复制代码
[me@linuxbox ~]$ find playground -type f -name 'file-B' -exec touch '{}' ';'

该命令更新所有名为file-B的文件。通过使用find命令,将所有文件与参照文件timestamp比对,识别出已更新过的文件:

bash 复制代码
[me@linuxbox ~]$ find playground -type f -newer playground/timestamp

结果中包含了全部100个file-B文件。因为我们在更新timestamp之后对playground目录中的所有文件执行了touch命令,所以它们的时间都比timestamp更新,可以使用-newer测试条件来识别。

最后,回到先前执行过的"错误的权限"测试,将其应用于playground目录:

bash 复制代码
[me@linuxbox ~]$ find playground \ ( -type f -not -perm 0600 \) -or \ ( -type d
-not -perm 0700 \)

该命令列出了playground中的100个子目录和2600个文件(还包括timestamp和playground,共计2702个)​,因为它们都不符合"正确的权限"的定义。利用find命令操作符和操作的知识,我们在命令中加入相关操作,为playground目录中的文件和子目录设置新的权限:

bash 复制代码
[me@linuxbox ~]$ find playground \ ( -type f -not -perm 0600 -exec chmod 0600
'{}' ';' \) -or \ ( -type d -not -perm 0700 -exec chmod 0700 '{}' ';' \)

依据日常经验,大家可能会觉得用两条命令(分别针对目录和文件)要比用这么长的复合命令更容易,不过知道可以像这样一次性完成操作也是件好事。这里的重点在于理解如何配合使用操作符和操作来执行一些实用的任务。

2.8、find命令选项

最后要讲的是一些可用于控制find命令查找范围的选项。在构建find表达式时,这些选项可以与其他测试条件和操作共同使用。下表列出了其中一些常用的find命令选项。

相关推荐
mounter6251 小时前
Linux 内核 binfmt_misc 深度全景解析:历史、架构、安全
linux·安全·linux kernel·kernel
我命由我123451 小时前
Windows 操作系统 - 启用 D 盘虚拟内存
java·运维·服务器·windows·学习·java-ee·运维开发
平头哥技术团队1 小时前
Day 13 | 调 line-height 和 margin:三处间距让名片页脱离模板感
开发语言·前端·javascript·学习·html5
具身AGI1 小时前
第一视角数据从哪来,物理AI 人类学习路线 的入口
人工智能·深度学习·学习
吴声子夜歌1 小时前
Linux命令——归档与备份
linux·运维·备份·归档
fangjianj2 小时前
Dell PowerEdge R930 物理服务器重装 Ubuntu Server 22.04 全流程实战记录
linux·服务器
fpcc2 小时前
计算机原理—程序的具体执行流程
linux
HackTwoHub2 小时前
BurpSuite2026.8专业(稳定版)下载Windows/Linux/Mac支持Java21以上(新增Burp AT智能体)
linux·运维·服务器·安全·macos·网络安全·自动化
晴天162 小时前
systemd 核心原理与实战-Day39
linux