Linux基础教程:find 与 xargs

find 命令的工作方式

    find命令的工作方式如下:沿着文件层次结构向下遍历,匹配符合条件的文件,并执行相应的操作。

find命令异常强大,因为它允许您按文件名、文件类型、用户甚至是时间戳查找文件。使用 find 命令,您不但可以找到具这些属性任意组合的文件,还可以对它找到的文件执行操作。

[注意:本文使用的 find 版本是 GNU 版本,因此,某些细节可能与其他版本的 find 有所不同。]

 

基本格式

    开始之前,我们先来看一下 find 命令的基本结构:

find   start_directory  test  options   criteria_to_match  action_to_perform_on_results

start_directory: find 命令所查找的路径,可指定多个路径。
1
2
# find . -print
#  find /  /etc  /usr  "*.c"

    以上命令, find 将在当前目录(”.”表示)中查找任何文件。

-print   指明打印出匹配文件的文件名(路径)。当使用 -print时, ‘n’作为用于分隔文件的定界符。

-print0 指明使用 ‘’ 作为定界符来打印每一个匹配的文件名。当文件名中包含空白符或换行符时,就非常有用了。

 

    如果用户没有相应权限,在使用find的时候,会生成很多错误信息: Permission denied 

我们可以把错误信息重定向,以保证结果清晰明了:

1
# find /etc -name "*.sh"  2>/dev/null

 

 

根据文件名或正则表达式匹配搜索

    选项 -name 参数指定了文件名所必须匹配的字符串。我们可以将通配符作为参数使用。注意,

find 还有一个选项 -iname (忽略大小写),该选项的作用和 -name类似,只不过在匹配文件名的时候忽略大小写。

注意: 你需要对通配符进行转义以确保它传递到 find  命令并且不被 shell 解释

1
2
3
4
5
# find . -name "*.sh"
./sct2.sh
./first.sh
./FIRST.sh
./one.sh

 如果想匹配多个条件中的一个,可以采用 OR 条件操作:

 

1
2
3
4
5
6
# find . ( -name "*.sh" -o -name "*.txt" ) -print
./sct2.sh
./a.txt
./first.sh
./FIRST.sh
./one.sh

    选项 -path 参数可以使用通配符来匹配文件路径或文件。-name总是用给定的文件名进行匹配。-path则将文件路径作为一个整体进行匹配。

    选项 -regex 的参数和-path类似,只不过 -regex是基于正则表达式来匹配文件路径的,This is  a  match on  the  whole path, not a search。类似的,-iregex 用于正则表达式匹配时忽略大小写。

 

 

基于目录深度的搜索: maxdepth, mindepth, 忽略某个目录

    find 命令在使用时会遍历所有的子目录。我们可以采用一些深度参数来限制 find 命令遍历的深度。 -maxdepth 和 -mindepth 就是这类参数。

    大多数情况下,我们只需在当前目录中进行搜索,无须再继续向下查找。对于这种情况,我们使用深度参数来限制find 命令向下查找的深度。如果只允许 find 在当前目录中查找,深度可以设置为1; 当需要向下两级时,深度可以设置为2; 其他情况可以一次类推。

    我们可以通过 -maxdepth 参数指定最大深度。与此类似,我们也可以指定一个最小的深度,使用 -mindepth参数设置最小深度。

    -maxdepth和-mindepth应该作为第一个选项出现。如果作为之后的选项,就可能会影响find的效率,因为它不得不进行一些不必要的检查。

 

     find 命令在使用时会遍历所有的子目录。使用find进行查找的时候,有时候需要忽略某些目录,可以使用 -prune 参数来进行过滤,但必须要注意忽略的路径参数必须紧跟着搜索的路径之后,否则该参数无法起作用

 

以下是指定搜索/home/carryf目录下的所有文件,但是会忽略/home/carryf/astetc的路径:

find /home/carryf -path “/home/carryf/astetc” -prune -o -type f -print

如果按照文件名来搜索则为:

find /home/carryf -path “/home/carryf/astetc” -prune -o -type f -name “cdr_*.conf” -print

如果要忽略两个以上的路径如何处理?

find /home/carryf ( -path “/home/carryf/astetc” -o -path “/home/carryf/etc” ) -prune -o -type f  -print

find /home/carryf ( -path “/home/carryf/astetc” -o -path “/home/carryf/etc” ) -prune -o -type f  -name “*.conf” -print

注意( 和) 前后都有空格。

 

基于文件类型搜索

    类UNIX系统将一切皆视为文件。文件具有不同的类型,例如普通文件、目录、字符设备、块设备、符号链接、套接字以及FIFO等。

    -type 选项可以对文件类型搜索进行过滤。以下是type可指定的类型:

wKiom1UUrI-D7rJeAABUXFrmIGk186.jpg

1
2
3
4
5
只列出所有的目录:
find . -type d -print
  
只列出普通文件:
find . -type f -print

基于时间进行搜索

    UNIX/Linux 文件系统中的每一个文件都有三种时间戳(timestamp),如下所示。

  • 访问时间(-atime, access time): 用户最近一次访问文件的时间。
  • 修改时间(-mtime, modify time): 文件内容最后一次被修改的时间。
  • 变化时间(-ctime,  change time): 文件元数据(metadata,例如文件权限或所有权)最后一次改变的时间。

-atime, -mtime, -ctime 可作为 find 的选项, 它们以整数值给出,计量单位是“”。这些整数值通常还带有 – 或 + ; – 表示 小于, + 表示 大于。

1
2
3
4
5
6
7
8
打印出在最近 7 天被访问过的所有文件:
find . -type f -atime -7 -print
  
打印出恰好在7天前被访问过的所有文件:
find . -type f -atime 7 -print
  
打印出超过 7 天被访问过的所有文件:
find . -type f -atime +7 -print

-atime, -mtime, -ctime 它们计量单位是“”。还有其他一些基于时间的参数是以“分钟”作为计量单位的。它们分别是:

  • -amin(访问时间)
  • -mmin(修改时间)
  • -cmin(变化时间)

 

 

基于文件大小搜索

    -size 选项可以根据文件大小过滤,基于文件大小可以这样搜索:

1
2
3
4
5
6
7
8
find . -type f -size +2k
# 大于 2KB 的文件
  
find . -type f -size -2k
# 小于 2KB 的文件
  
find . -type f -size 2k
# 等于 2KB 的文件, 需要注意的是:可能会向上进行圆整, -size 7k , 可能会找出 6.8k 的文件

除了k之外,还可以用其他文件大小单位:

  • b    块(512字节)
  • c    字节
  • w    字(2字节)
  • k    千字节
  • M    兆字节
  • G    吉字节

 

基于文件权限和所有权搜索

    find 命令

 

-perm    -mode

              All  of  the  permission  bits mode are set for the file.  Symbolic modes are

              accepted in this form, and this is usually the way in which would want to use

              them.  You must specify ‘u’, ‘g’ or ‘o’ if you use a symbolic mode.   See the

              EXAMPLES section for some illustrative examples.

-perm    /mode

              Any of the permission bits mode are set for the  file.   Symbolic  modes  are

              accepted  in  this  form.  You must specify ‘u’, ‘g’ or ‘o’ if you use a sym-

              bolic mode.  See the EXAMPLES section for some illustrative examples.  If  no

              permission bits in mode are set, this test matches any file (the idea here is

              to be consistent with the behaviour of -perm -000).

-perm    +mode

              Deprecated, old way of searching for files with any of the permission bits in

              mode  set.   You should use -perm /mode instead. Trying to use the ‘+’ syntax

              with symbolic modes will yield surprising results.  For example, ‘+u+x’ is  a

              valid  symbolic  mode (equivalent to +u,+x, i.e. 0111) and will therefore not

              be evaluated as -perm +mode but instead as the  exact  mode  specifier  -perm

              mode  and  so  it  matches files with exact permissions 0111 instead of files

              with any execute bit set.  If you found this paragraph confusing, you’re  not

              alone  –  just  use  -perm  /mode.  This form of the -perm test is deprecated

              because the POSIX specification requires the interpretation of a leading  ‘+’

              as being part of a symbolic mode, and so we switched to using ‘/’ instead.

 

find -perm参数的详细说明:

-perm

    MODE:  精确匹配,文件权限“刚好等于”MODE, 必须和指定的权限位 一模一样

    -MODE: 文件权限能完全包含此MODE时才符合条件,“必须全部包含”了所有指定的相应权限位即可

    /MODE: 任意一位匹配即满足条件,“任意包含一个指定权限位”的文件都符合。

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
# ls -l
total 8
-rw-r--r--. 1 root root 77 Mar 27 23:14 a.txt
-------r--. 1 root root  0 Mar 27 23:15 b.txt
-rwxr-xr-x. 1 root root 32 Mar 27 23:14 first.sh
  
MODE: 必须完全精确匹配, 只有权限为 644 的才会找到
# find . -perm 644
./a.txt
  
-MODE: 对应的权限位必须完全包含。rw-r--r-- , 必须包含这几个权限位即可符合条件
# find . -perm -644
.
./a.txt
./first.sh
  
/MODE: 对应的权限位,只要匹配 MODE 任意一位即可。 rw-r--r--
# r--------    匹配
# -w-------    匹配
# ---r-----    匹配
# ------r--    匹配
# rwxr-xr-x    匹配
# find . -perm /644 
.
./a.txt
./first.sh
./b.txt

 

 

结合find执行命令或动作

    find 命令可以借助选项 -exec 与其他 shell 命令进行结合。相应语法格式为  -exec  command {} ;

注意 {} 和  ; 之间的空格。{ } 代表find找到的文件,表示转义 “;” 表示本行指令结束。因为“;”在bash环境下是有特殊意义,因此需要转义。
    -ok: 和-exec的作用相同,只不过以一种更为安全的模式来执行该参数所给出的shell命令,在执行每一个命令之前,都会给出提示,让用户来确定是否执行。

示例如下:

1
# find . -type f -exec ls -l {} ;

    在这个命令中, {} 是一个特殊的字符串,与 -exec 选项结合使用。对于每一个匹配的文件,{}会被替换成相应的文件名。

-exec 结合多个命令

    我们无法再 -exec 选项后直接使用多个命令。它只能接受单个命令,不过我们可以耍一个小花招。把多个命令写到一个shell脚本中,然后在 -exec 中使用这个脚本。

 

 

组合多个条件进行搜索

     我们可以同时指定多个 选项, 比如: find . -type f  -size 1M  ; 我们同时指定了 -type , -size 选项。默认情况下,如果我们同时指定了多个选项, 那么选项之间默认是通过 “” 条件组合。 

( expr )

              Force precedence.  Since parentheses are special to the shell, you will  nor-

              mally need to quote them.  Many of the examples in this manual page use back-

              slashes for this purpose: ‘(…)’ instead of ‘(…)’.

 

expr1 expr2

              Two  expressions in a row are taken to be joined with an implied “and”; expr2

              is not evaluated if expr1 is false.

 

find 支持三种逻辑组合方式:

  • -a        -and
  • -o        -or
  • !           -not

 

 

find命令的好基友 xargs

    有些命令只能以命令行参数的形式接受数据,而无法通过stdin接受数据流。这种情况下,我们没法用管道来提供哪些只有通过命令行参数才能提供的数据。

    xargs是一个很有用的命令,它擅长将标准输入数据转换成命令行参数。xargs能够处理stdin并将其转换为特定命令的命令行参数。

    xargs命令应该紧跟在管道操作符之后。它以标准输入作为主要的源数据流,并使用stdin并通过提供命令行参数来执行其他命令。xargs命令把从stdin接收到的数据重新格式化(xargs 默认是以空白字符 (空格, TAB, 换行符) 来分割记录),再将其作为参数提供给其他命令。 

例如: command  |  xargs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
-d 选项: 指定参数的分隔符
# echo "splitXsplitXsplitXsplit" | xargs -d X
split split split split
  
-n 选项: 指定每行最大的参数数量 n, 我们可以将任何来自 stdin 的文本划分成多行,
每行 n 个参数。每一个参数都是由“”(空格)隔开的字符串。空格是默认的定界符。
# echo "splitXsplitXsplitXsplit" | xargs -d X -n 2
split split
split split
  
-I 选项, 可以指定一个替换字符串,这个字符串在xargs扩展时会被替换掉。
当 -I 与 xargs 结合使用时,对于每一个参数,命令都会被执行一次。
相当于指定一个占位符。
# cat args.txt | xargs -I {} ./cecho.sh -p {} -l
  
-I 指定了替换字符串。对于每一个命令参数,字符串{}会被从 stdin 读取到的参数所替换。

 

为什么要使用 xargs ?

    在使用find命令的-exec选项处理匹配到的文件时, find命令将所有匹配到的文件一起传递给exec执行。但有些系统对能够传递给exec的命令长度有限制,这样在find命令运行几分钟之后,就会出现溢出错误。错误信息通常是“参数列太长”或“参数列溢出”。这就是xargs命令的用处所在,特别是与find命令一起使用。

    find命令把匹配到的文件传递给xargs命令,而xargs命令每次只获取一部分文件而不是全部,不像-exec选项那样。这样它可以先处理最先获取的一部分文件,然后是下一批,并如此继续下去。

    在有些系统中,使用-exec选项会为处理每一个匹配到的文件而发起一个相应的进程,并非将匹配到的文件全部作为参数一次执行;这样在有些情况下就会出现进程过多,系统性能下降的问题,因而效率不高;而使用xargs命令则只有一个进程。另外,在使用xargs命令时,究竟是一次获取所有的参数,还是分批取得参数,以及每一次获取参数的数目都会根据该命令的选项及系统内核中相应的可调参数来确定。

 

来看看xargs命令是如何同find命令一起使用的,并给出一些例子。

find . -type f -print | xargs file     查找系统中的每一个普通文件,然后使用xargs命令来测试它们分别属于哪类文件

find . -type f -print | xargs grep “hostname”     用grep命令在所有的普通文件中搜索hostname这个词

find ./ -mtime +3 -print|xargs rm -f –r     删除3天以前的所有东西 (find . -ctime +3 -exec rm -rf {} ;)

find ./ -size 0 | xargs rm -f &     删除文件大小为零的文件

find命令配合使用exec和xargs可以使用户对所匹配到的文件执行几乎所有的命令。

看起来还不错, 不过 。。。

1
2
3
4
5
6
7
8
[root@skype tmp]# ls
file 1.txt  file 2.txt
  
[root@skype tmp]# find . -name '*.txt' | xargs rm
rm: cannot remove `./file': No such file or directory
rm: cannot remove `1.txt': No such file or directory
rm: cannot remove `./file': No such file or directory
rm: cannot remove `2.txt': No such file or directory

j_0065.gif tell me way ? 

    原因其实很简单, xargs 默认是以空白字符 (空格, TAB, 换行符) 来分割记录的, 因此文件名 ./file 1.txt 被解释成了两个记录 ./file 和 1.txt, 不幸的是 rm 找不到这两个文件.为了解决此类问题, 聪明的人想出了一个办法,  find 在打印出一个文件名之后接着输出一个 NULL 字符 (‘’)而不是换行符, 然后再告诉 xargs 也用 NULL 字符来作为记录的分隔符. 这就是 find  -print0  xargs -0 的来历吧.

如果拯救?

1
[root@skype tmp]# find . -name '*.txt' -print0 | xargs -0 rm

 

 -print0

              True;  print  the  full  file name on the standard output, followed by a null

              character (instead of the newline character that -print uses).   This  allows

              file  names  that  contain  newlines or other types of white space to be cor-

              rectly interpreted by programs that process the  find  output.   This  option

              corresponds to the -0 option of xargs.

 

Add a Comment

电子邮件地址不会被公开。 必填项已用*标注