我想使用 find 递归搜索文件并将它们打印到文件中。我设想这样的命令:
find /local/data/ --exclude 'database/session*' --include='database/session_*.db' > temp.txt
此命令不起作用。如何使用有效的排除和包含?
答案1
总结
类似于
find /local/data/ \
! -path '/local/data/database/session*' \
-o -path '/local/data/database/session_*.db'
前言
我知道的实现中没有简单的--include
和指令。无论如何,您可以构建一个按您的意愿工作的测试序列,因为中的测试机制是经过精心设计的,允许基于任何标准(即不一定基于路径名)的任何测试(甚至是自定义测试)。要执行您想要的操作,您需要将排除/包含模式转换为测试序列。要正确执行此操作,您需要知道它是如何工作的。它的机制比排除/包含的概念更通用。--exclude
find
find
find
在这里我将主要依靠POSIX 规范find
(所有引文均来自本文档)。超出本规范的实现会扩展该工具,而不会改变其总体理念。
理论
为了理解和有效使用,find
您需要了解以下几点:
术语:
- 很少有可能选项(例如
-L
)可能出现在 之后find
。对于此答案的目的而言,它们并不重要。 - 然后有一个或多个起点.
/local/data/
在您的示例中是起点。某些实现允许零起点(则.
或./
为默认起点)。 - 接下来的一切构成了一个表达。表达式由零个或多个受支持的操作数组成:基本操作数如
-name
、-exec
;操作符如-o
、(
(通常应将其转义或加引号以保护其免受 shell 攻击)或!
。其中一些需要自定义的附加操作数(例如模式),这些操作数也属于表达式。
- 很少有可能选项(例如
表达式中的几乎所有内容都是测试。我的 Ubuntu 中的 GNU 手册
find
将支持的操作数分为几类:测试、操作等。但大多数操作数仍可视为测试;即任何主要操作都返回 true 或 false,这会影响find
下一步的操作。在此回答中,我以非常广泛的意义使用了“测试”一词。find
从指定的起点开始,按照特定顺序递归下降目录层次结构。某些操作数可以改变顺序 (-depth
) 甚至缩短顺序 (-prune
)。find
计算每个表达式的值文件分别地。find
从左到右计算表达式。如果此操作不影响整体输出(不仅仅是输出到 stdout,注意-exec
可以做任何事情),该工具可能会重新排列测试,一些实现这样做是为了提高性能;即使这样,表达式也应该像从左到右计算一样工作。不过,有些操作数无论在表达式中的位置如何都可以工作(-depth
,-xdev
)。对于给定文件,表达式的某些部分可能根本无法求值。运算符
-a
、-o
、(
+定义表达式的逻辑。)
!
可以使用以下运算符来组合原色(按优先级从高到低的顺序):
( expression )
如果expression
为真,则为真。! expression
对基本运算符进行否定;一元非运算符。expression [-a] expression
主元的结合;AND 运算符由两个主元的并置暗示或由可选运算符明确表示-a
。如果第一个表达式为假,则不应评估第二个表达式。expression -o expression
主元交替;或运算符。如果第一个表达式为真,则不应计算第二个表达式。想象一下
-test1
,-test2
并且-test3
是测试find
理解。让表达式为! -test1 -test2 -o -test3
相当于
( ( ! -test1 ) -a -test2 ) -o -test3
在 shell 中完整命令分别为:
find /starting/point ! -test1 -test2 -o -test3 find /starting/point \( \( ! -test1 \) -a -test2 \) -o -test3
可能的结果:
-test1
对每个测试文件进行评估。- 如果
-test1
为假,( ! -test1 )
则为真。然后-test2
进行评估,因为这就是-a
工作原理。- 如果
-test2
为假,则外括号中的表达式为假。然后-test3
进行求值,因为这就是-o
工作原理。- 如果
-test3
为假,则整个表达式为假。 - 如果
-test3
为真,则整个表达式为真。
- 如果
- 如果
-test2
为真,则外括号中的表达式为真。然后-test3
不进行求值,因为这就是-o
工作原理。整个表达式为真。
- 如果
- 如果
-test1
为真,( ! -test1 )
则为假。然后-test2
不进行求值,因为这是-a
工作原理。外括号中的表达式为假。然后-test3
进行求值,因为这是-o
工作原理。- 如果
-test3
为假,则整个表达式为假。 - 如果
-test3
为真,则整个表达式为真。
- 如果
- 如果
注意 从逻辑上讲
( ( NOT A ) AND B ) OR C
等价于C OR ( B AND ( NOT A ) )
,但与find
下列表达式并不等价,一般来说它们两两不同:! -test1 -test2 -o -test3 -test2 ! -test1 -o -test3 -test3 -o ! -test1 -test2 -test3 -o -test2 ! -test1
如果有一个或多个测试,则尤其如此
-exec
。通常-exec
用于有条件地做某事(例子),因此它将在其他测试(条件)之后,我们宁愿说它是一个动作,而不是一个测试。但你可以用-exec
(例子),这非常强大;在这种情况下,-exec
甚至可能是第一个测试,即始终要评估的测试。不仅逻辑结果(真或假)会-exec
执行find
或跳过文件的后续测试。什么-exec
(例如,假设它删除了一些附带文件)可能会以不明显的方式影响后续测试(针对同一文件甚至其他文件)。在某些情况下
-print
会隐式添加:如果不存在表达式,
-print
则应使用作为表达式。否则,如果给定的表达式不包含任何原语-exec
、-ok
或-print
,则给定的表达式应被有效地替换为:( given_expression ) -print
笔记
- 在这种情况下,
-print
当且仅当给定表达式求值为真时,才会求值(执行)。上面,我写到“整个表达式为假”或“整个表达式为真”,我的意思是对于隐式-print
(如果适用)来说什么是重要的。 - 实现中可以使用其他(非 POSIX)主键来扩展集合“
-exec
,-ok
, ”。-print
- 在这种情况下,
解决方案
问题是关于基于路径名的排除/包含。以下主要内容很有用:
-name pattern
pattern
如果当前路径名的基本名称使用模式匹配符号 匹配,则主要将评估为真 […]
-path pattern
pattern
如果当前路径名使用模式匹配符号匹配, 则主路径将评估为真[…]
-prune
主路径应始终评估为真;find
如果当前路径是目录,则不下降当前路径名。如果-depth
指定了主路径,则 -prune 主路径将不起作用。
(“基本名称”或“路径名称”等术语的定义这里。
实现中可以添加其他有用的原色(例如-regex
)-iname
。
通常-prune
是排除给定目录内容的正确方法(带或不带目录本身)。但它完全阻止find
进入目录;因此,如果您无论如何都想在目录中查找(包含)某些文件,那么您就不能使用-prune
。
我想你想要这个:
- 打印目录层次结构中从 开始的每个文件的路径名
/local/data/
, - 但不要如果匹配
/local/data/database/session*
, - 但要如果匹配
/local/data/database/session_*.db
。
以下find
命令可以完成此操作:
find /local/data/ \
! -path '/local/data/database/session*' \
-o -path '/local/data/database/session_*.db'
换行符之前的位置\
告诉 shell 命令将在下一行继续。引用很重要(您可能知道,您在问题中引用了)。
它的工作原理如下:
- 对于起点下(包括起点)但不匹配排除模式的每个文件,
! -path …
为真;不执行第二次测试,整个表达式为真。 - 对于起点下(包括起点)的每个文件并且匹配排除模式,
! -path …
都是错误的;只有这样才会执行第二次测试。- 如果第二个测试为真,则整个表达式为真。
- 如果第二个测试结果为假,则整个表达式也为假。
笔记:
-print
这个是添加隐式的情况。- 按相反顺序进行的这些测试同样有效。
一般情况
使用括号-a
,-o
您!
可以创建相当复杂的排除+包含方案。特别是:
- 嵌套(例如 排除
./foo/*
,但 包括./foo/bar/*
,但 排除./foo/bar/baz/*
,但...); - 基于路径名以外的标准(例如,完全排除根拥有的目录)。
尽管创建完美地实现复杂方案的表达式可能并不容易。
陷阱
模式中的元字符(例如
*
)不会对/
或进行.
特殊处理。片段session_*.db
匹配session_5.db
,它也匹配session_foo/bar/baz.db
。在可以使用的情况下
-prune
,记住-prune
评估为真。使用隐式-print
这可能会让你感到惊讶这就是为什么我写道“-prune
排除给定目录内容的正确方法(有或没有目录本身)”。在您可以使用的情况下
-prune
,请确保在需要时对其进行评估。例子:
mkdir -p test/ab/a; cd test find . -name 'a*' -print -o -name '*b' -prune #1 find . -name '*b' -prune -o -name 'a*' -print #2 find . -name '*b' -prune -print -o -name 'a*' -print #3 find . \( -name '*b' -prune -o -name 'a*' \) -print #4 find . -name '*b' -prune -o -name 'a*' #5
ab
在第一种情况下,将打印指定的目录,并不是修剪。在第二种情况下,它将被修剪并且不打印。在第三种情况下,它将被修剪并打印一次。第四种情况相当于第三种情况,-print
已放在括号后面(就像数学中的公因数)。第五种情况相当于第四种情况,-print
是隐式的。第一种情况是一个更普遍的问题(错误)的例子,其中某些文件(这里是
ab
目录)从未达到为其设计的测试和正确的操作,因为它意外地与为其他文件设计的早期测试匹配,并触发了不必要的操作。使用的路径名
-path
是find
“认为”的路径名,而不是realpath
打印的路径名。模式必须考虑到这一点。例子:
cd /bin && find . -path '/bin*' # will find nothing cd /bin && find . -path '.*' # will find "everything" cd /bin && find /bin -path '/bin*' # will find "everything" cd /bin && find /bin -path '.*' # will find nothing
类似地,对于起点,所使用的基本名称
-name
取决于起点的确切表示。虽然存在一些极端情况,但仍然如此:/
对于/
,,///
等等////
。.
对于.
,,,,等./
/.
/bin/.
/bin/../.
..
对于..
、、、等等/..
/../../
///bin/..
每个起点定义一个单独的层次结构。该工具不关心层次结构是否重叠。
例如:如果
/bin/bash
和/bin/dash
存在,则以下命令将找到bash
四次(使用三个不同的路径名)和dash
三次(使用两个不同的路径名):cd /bin && find . /bin /bin ../bin/bash -name '[bd]ash'