Top

D14:常见任务和基本工具之四

搜索文件

本节涉及的命令有: 以下命令用处理生成的文件列表: 此外还有两个命令可用于实例操作:

locate 查找文件的简单方法

locate程序对路径名执行快速数据库搜索,然后输出与给定子字符串匹配的每个名称。比如:
[me@linuxbox ~]$ locate bin/zip
/usr/bin/zip
/usr/bin/zipcloak
/usr/bin/zipgrep
/usr/bin/zipinfo
/usr/bin/zipnote
/usr/bin/zipsplit	
locate将搜索路径名数据库,并返回所有包含bin/zip的结果。如果输出结果太多,可以与grep等工具结合:
[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程序已经存在很多年了,现代Linux发行版中最常见的两个版本是slocatemlocate,不过它们通常通过名称为locate的符号链接访问。一些版本包括正则表达式匹配和通配符支持。

locate数据库由另一个名为updatedb的程序创建。通常它作为cron作业定期运行。大多数系统每天运行一次updatedb。所以有时候可能无法找到新建的文件,此时可以用超级用户的身份手动运行updatedb来更新数据库。

find 查找文件的艰难之路

find程序会根据各种属性在给定目录(及其子目录)中搜索文件。
最简单的用法是在find后面指定一个或多个要搜索的目录名称,例如以下命令生成主目录列表:
find ~
在大多数活跃用户中,这将生成一个大列表。由于列表被发送到标准输出,我们可以其导入其他程序。例如用wc来计算文件的数量
find ~ | wc -l
find通过optionstestactions可以识别满足特定标准的文件。

test

比如说,我们希望从搜索中获得目录数量:
find ~ -type d | wc -l
而以下命令将统计有多少个文件:
find ~ -type f | wc -l
type常用的选项如下:
文件类型 说明
b 块特殊设备文件
c 字符特殊设备文件
d 目录
f 普通文件
l 符号链接
还可以添加一些附加测试,按文件大小和文件名进行搜索。例如,查找所有与通配符*.jpg匹配的常规文件,且大于1M字节:
find ~ -type f -name "*.jpg" -size +1M | wc -l
此例添加了-name测试,后面跟通配符模式。注意,通配符用引号括起来,以防止shell扩展路径名。
然后添加-size测试,后面跟字符串“+1M”。前导的加号表示我们要查找大于指定数字的文件。如果前导使用减号,将表示小于指定的数字。如果不使用符号,则表示精确匹配。
下表列出用于指定单位的字符:
字符 >单位
b 512字节块,如果未指定单位,此为默认值。
c 字节
w 双字节
k 千字节
M 兆字节
G 千兆字节
find支持大数test。下表列出常见情况(非全部),在需要数字参数的情况下,可以用+和-代表大于或小于。
test 含义
-cmin n 匹配内容或属性在n分钟前最后一次修改的文件或目录。
要指定少于n分钟前的时间,请使用-n;要指定多于n分钟前的时间,请使用+n。
cnewer file 匹配其内容或属性最近一次被修改的文件或目录,而不是文件的内容或属性。
ctime n 匹配其内容或属性在n*24小时前最后一次修改的文件或目录。
-empty 匹配空文件和目录
-group name 匹配属于group的文件和目录。group可以用组名或组ID。
-iname pattern 与-name类似,但不区分大小写。
-inum n 使用inode编号匹配文件。这有助于找到指向特定inode的所有硬链接。
-mmin n 匹配n分钟前最后一次修改其内容的文件或目录。
-mtime n 匹配内容在n*24小时前最后一次修改的文件或目录。
-name pattern 使用pattern匹配文件名。
-newer file 匹配内容比指定文件最近修改的文件和目录。
这在编写执行文件备份的shell脚本时非常有用。
每次备份时,都要更新文件(如日志),然后使用“查找”确定自上次更新以来哪些文件已更改。
-nouser 匹配不属于有效用户的文件和目录。
这可用于查找属于已删除帐户的文件,或检测攻击者的活动。
-nogroup 匹配不属于有效组的文件和目录。
-perm mode 匹配权限设置为指定mode的文件或目录。
mode可以用八进制或符号表示。
-samefile name 类似-inum测试。匹配与文件名共享相同inode编号的文件。
-size n 匹配文件尺寸n
-type c 匹配文件类型t
-user name 匹配属于用户name的文件或目录。
用户可以使用用户名或数字ID表示。

操作者

类似逻辑运算符。举例:
find ~ \( -type f -not -perm 0600 \) or \( -type d -not -perm 0700 \)
以上查找所有具有非0600权限的文件和具有非0700权限的普通文件。以下是所有可用的逻辑运行符:
操作者 说明
-and 如果操作者两侧的test均为真,则匹配。可以缩写为-a。
若无指定的操作者,默认隐含-and。
-or 如果操作者两侧的test有一个为真,则匹配。可缩写为-o。
-not 如果操作者之后的test为假,则匹配。可缩写为!。
( ) 将test和运算符组合在一起,以形成更大的表达式。这用于控制逻辑计算的优先级。
默认情况下,find命令从左向右计算。
由于括号对shell有特殊意义,因此在命令行上使用括号是必须进行转义,以便将其作为参数传递。

预定义操作

find允许根据搜索结果执行操作。有一组预定义的操作和几种应用用户定义的操作方法。下表是一些预定义操作:
操作 说明
-delete 删除当前匹配的文件。
-ls 对匹配的文件执行等效的ls -dils。输出被发送到标准输出。
-print 将匹配文件的完整路径名输出到标准输出。如果未指定其他操作,则这是默认操作。
-quit 匹配完成就退出(不显示结果)。
以下示例删除主目录中所有.bak结尾的文件:
find ~ -type -f -name '*.bak' -delete
警告,使用-delete操作时应格外小心。始终先测试命令,用-print替换-delete以确认搜索结果。
find ~ -type f -name '*.bak' -print
由于操作是从左向右的顺序,所以,以上命令和以下命令的执行结果是不同的:
find ~ -print -and -type f -name '*.bak'

用户定义的操作

除了预定义的操作,还可以调用任意命令。传统的方法是使用-exec操作,语法为:
-exec command {} ;
下面是一个使用-exec来执行前面-delete操作的示例:
-exec rm '{}' ';'
同样的,由于大括号和分号对于shell有特殊意义,因此必须给它们加引号或转义。
还可以交互执行用户定义的操作。通过使用-ok操作代替-exec,用户在执行每个指定命令之前都会收到提示。
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。在执行ls命令之前,使用-ok操作会提示用户。

提高效率

使用-exec操作时,每次找到匹配的文件时,它都会启动指定命令的新实例。
有时,我们可能更希望合并所有搜索结果,并启动该命令的单个实例。

以下两个命令得到的结果相同,但第二行系统只需执行ls命令一次:
find ~ -type f -name 'foo*' -exec ls -l '{}' ';'
find ~ -type f -name 'foo*' -exec ls -l '{}' +

xargs

xargs命令接受来自标准输入的输入,并将其转换为指定命令的参数列表。例如:
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中,xargs反过来为ls命令构造一个参数列表,然后执行它。

注意,shell无法接受太长的命令。当命令行超出系统支持的最大长度时,xargs将以可能的最大参数执行指令的命令,然后重复此过程,直到标准输入耗尽。
使用xargs --show-limits可以查看命令行的极限大小。

类Unix系统允许文件名中使用空格(甚至换行符)。嵌入的空格将被视为分隔符,xargs生成的命令将把每个空格分隔的单词解释为单独的参数。
为了解决这个问题,find和xargs允许可选地使用空字符作为参数分隔符。
空字符在ASCII中定义为数字0表示地字符。find命令使用-print0,而xargs命令使用--null(或-0)选项。例如:
find ~ -iname '*.jpg' -print0 | xargs --null ls -l

回到操练场

首先,创建一个有很多子目录和文件地操练场:
[me@linuxbox ~]$ mkdir -p playground/dir-{001..100}
[me@linuxbox ~]$ touch playground/dir-{001..100}/file-{A..Z}	
以上两行代码创建目录,它包含100个子目录,每个子目录包含26个空文件。
touch命令通常用于设置或更新文件的访问、更改和修改时间。但如果文件名参数是不存在的,则会创建一个空文件。

sh、csh、tcsh中将{001..100}解释为实际的字符串,不会展开。

[me@linuxbox ~]$ find playground -type f -name 'file-A'
以上命令查找100个名为file-A的文件。
ls不同,find命令不提供对结果的排序。其顺序由存储设备的布局决定。我们可以通过以下命令确认我们确实创建了100个文件:
[me@linuxbox ~]$ find playground -type f -name 'file-A' | wc -l

接下来,让我们看看根据文件的修改时间查找文件。这有助于创建备份或按时间顺序组织文件。
首先创建一个参考文件用于比较修改时间:
[me@linuxbox ~]$ touche playground/timestamp
以上创建一个名为timestamp的空文件,并将其修改时间设置为当前时间。可以使用stat命令验证这一点。
statls命令的增强版本。
[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检查文件,将看到文件的时间已经更新:
[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命令更新操练场的文件:
[me@linuxbox ~]$ find playground -type f -name 'file-B' -exec touch '{}' ';'
以上命令更新playground目录下所有文件名称为file-B的文件。下一步使用find命令通过将所有文件与参考文件timestamp进行比较来识别更新的文件:
[me@linuxbox ~]$ find playground -type f -newer playground/timestamp

[me@linuxbox ~]$ find playground \( -type f -not -perm 0600 \) -or \( -type d -not -perm 0700 \)
以上命令列出所有100个目录和2600个文件。以下命令将所有权限不是0600的文件修改成0600;将所有权限不是0700的目录修改称0700:
find playground \( -type f -not -perm 0600 -exec chmod 0600 '{}' ';' \) -or \( -type d -not -perm 0700 -exec chmod 0700 '{}' ';' \)

选项

下表列出了构造find表达式的常用选项:
选项 说明
-depth 指示find命令先处理目录文件,在处理目录本身。当指定-delete操作时,将自动应用此选项。
-maxdepth levels 设置在执行测试和操作时查找将下降到目录树中的最大级别数。
-mindepth levels 设置在执行测试和操作时查找将下降到目录树中的最小级别数。
-mount 指示find不遍历挂载在其他文件系统上的目录。
-noleaf 指示find不会基于搜索类Unix文件系统的假设来优化其搜索。
在扫描DOS/Windows文件系统和CD-ROM时,需要这样做。