关于Linux:如何让”find”忽略.svn目录?

How can I get `find` to ignore .svn directories?

我经常使用find命令来搜索源代码、删除文件等。令人恼火的是,因为Subversion将每个文件的副本存储在其.svn/text-base/目录中,我的简单搜索最终会得到许多重复的结果。例如,我想在多个messages.hmessages.cpp文件中递归搜索uint

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
# find -name 'messages.*' -exec grep -Iw uint {} +
./messages.cpp:            Log::verbose <<"Discarding out of date message: id" << uint(olderMessage.id)
./messages.cpp:    Log::verbose <<"Added to send queue:" << *message <<": id" << uint(preparedMessage->id)
./messages.cpp:                Log::error <<"Received message with invalid SHA-1 hash: id" << uint(incomingMessage.id)
./messages.cpp:            Log::verbose <<"Received" << *message <<": id" << uint(incomingMessage.id)
./messages.cpp:            Log::verbose <<"Sent message: id" << uint(preparedMessage->id)
./messages.cpp:        Log::verbose <<"Discarding unsent message: id" << uint(preparedMessage->id)
./messages.cpp:        for (uint i = 0; i < 10 && !_stopThreads; ++i) {
./.svn/text-base/messages.cpp.svn-base:            Log::verbose <<"Discarding out of date message: id" << uint(olderMessage.id)
./.svn/text-base/messages.cpp.svn-base:    Log::verbose <<"Added to send queue:" << *message <<": id" << uint(preparedMessage->id)
./.svn/text-base/messages.cpp.svn-base:                Log::error <<"Received message with invalid SHA-1 hash: id" << uint(incomingMessage.id)
./.svn/text-base/messages.cpp.svn-base:            Log::verbose <<"Received" << *message <<": id" << uint(incomingMessage.id)
./.svn/text-base/messages.cpp.svn-base:            Log::verbose <<"Sent message: id" << uint(preparedMessage->id)
./.svn/text-base/messages.cpp.svn-base:        Log::verbose <<"Discarding unsent message: id" << uint(preparedMessage->id)
./.svn/text-base/messages.cpp.svn-base:        for (uint i = 0; i < 10 && !_stopThreads; ++i) {
./virus/messages.cpp:void VsMessageProcessor::_progress(const string &fileName, uint scanCount)
./virus/messages.cpp:ProgressMessage::ProgressMessage(const string &fileName, uint scanCount)
./virus/messages.h:    void _progress(const std::string &fileName, uint scanCount);
./virus/messages.h:    ProgressMessage(const std::string &fileName, uint scanCount);
./virus/messages.h:    uint        _scanCount;
./virus/.svn/text-base/messages.cpp.svn-base:void VsMessageProcessor::_progress(const string &fileName, uint scanCount)
./virus/.svn/text-base/messages.cpp.svn-base:ProgressMessage::ProgressMessage(const string &fileName, uint scanCount)
./virus/.svn/text-base/messages.h.svn-base:    void _progress(const std::string &fileName, uint scanCount);
./virus/.svn/text-base/messages.h.svn-base:    ProgressMessage(const std::string &fileName, uint scanCount);
./virus/.svn/text-base/messages.h.svn-base:    uint        _scanCount;

我怎样才能告诉find忽略.svn目录?

更新:如果您将SVN客户端升级到1.7版,这不再是问题。

A key feature of the changes introduced in Subversion 1.7 is the centralization of working copy metadata storage into a single location. Instead of a .svn directory in every directory in the working copy, Subversion 1.7 working copies have just one .svn directory—in the root of the working copy. This directory includes (among other things) an SQLite-backed database which contains all of the metadata Subversion needs for that working copy.


为什么不只是

1
find . -not -iwholename '*.svn*'

-not谓词否定路径中任何位置具有.svn的所有内容。

所以在你的情况下

1
find -not -iwholename '*.svn*' -name 'messages.*' -exec grep -Iw uint {} + \;


如下:

1
find . -path '*/.svn*' -prune -o -print

或者,基于目录而不是路径前缀:

1
find . -name .svn -a -type d -prune -o -print


为了寻找,我建议你看看ACK?它是一个源代码感知的find,因此会自动忽略许多文件类型,包括源代码存储库信息,如上面所述。


要忽略.svn.git和其他隐藏目录(以点开头),请尝试:

1
find . -type f -not -path '*/\.*'

但是,如果使用find的目的是在文件中进行搜索,则可以尝试使用以下命令:

  • git grep—专门设计的命令,用于在git存储库中搜索模式。
  • ripgrep—默认情况下忽略.gitignore中指定的隐藏文件和文件。

相关:如何在Linux上找到包含特定文本的所有文件?


以下是我对您的案例所做的:

1
find . -path .svn -prune -o -name messages.* -exec grep -Iw uint {} +

Emacs的rgrep内置命令忽略了.svn目录,以及在执行find | grep时可能不感兴趣的更多文件。以下是它默认使用的内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
find . \( -path \*/SCCS -o -path \*/RCS -o -path \*/CVS -o -path \*/MCVS \
          -o -path \*/.svn -o -path \*/.git -o -path \*/.hg -o -path \*/.bzr \
          -o -path \*/_MTN -o -path \*/_darcs -o -path \*/\{arch\} \) \
     -prune -o \
       \( -name .\#\* -o -name \*.o -o -name \*\~ -o -name \*.bin -o -name \*.lbin \
          -o -name \*.so -o -name \*.a -o -name \*.ln -o -name \*.blg \
          -o -name \*.bbl -o -name \*.elc -o -name \*.lof -o -name \*.glo \
          -o -name \*.idx -o -name \*.lot -o -name \*.fmt -o -name \*.tfm \
          -o -name \*.class -o -name \*.fas -o -name \*.lib -o -name \*.mem \
          -o -name \*.x86f -o -name \*.sparcf -o -name \*.fasl -o -name \*.ufsl \
          -o -name \*.fsl -o -name \*.dxl -o -name \*.pfsl -o -name \*.dfsl \
          -o -name \*.p64fsl -o -name \*.d64fsl -o -name \*.dx64fsl -o -name \*.lo \
          -o -name \*.la -o -name \*.gmo -o -name \*.mo -o -name \*.toc \
          -o -name \*.aux -o -name \*.cp -o -name \*.fn -o -name \*.ky \
          -o -name \*.pg -o -name \*.tp -o -name \*.vr -o -name \*.cps \
          -o -name \*.fns -o -name \*.kys -o -name \*.pgs -o -name \*.tps \
          -o -name \*.vrs -o -name \*.pyc -o -name \*.pyo \) \
     -prune -o \
     -type f \( -name pattern \) -print0 \
     | xargs -0 -e grep -i -nH -e regex

它忽略大多数版本控制系统创建的目录,以及为许多编程语言生成的文件。您可以创建一个调用此命令的别名,并为您的特定问题替换findgrep模式。


GNU查找

1
find .  ! -regex".*[/]\.svn[/]?.*"


为此,我使用grep。把这个放进你的~/.bashrc

1
export GREP_OPTIONS="--binary-files=without-match --color=auto --devices=skip --exclude-dir=CVS --exclude-dir=.libs --exclude-dir=.deps --exclude-dir=.svn"

grep在调用时自动使用这些选项


创建名为~/bin/svnfind的脚本:

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
28
29
30
31
32
33
34
35
36
37
38
#!/bin/bash
#
# Attempts to behave identically to a plain `find' command while ignoring .svn/
# directories.

OPTIONS=()
PATHS=()
EXPR=()

while [[ $1 =~ ^-[HLP]+ ]]; do
    OPTIONS+=("$1")
    shift
done

while [[ $# -gt 0 ]] && ! [[ $1 =~ '^[-(),!]' ]]; do
    PATHS+=("$1")
    shift
done

# If user's expression contains no action then we'll add the normally-implied
# `-print'.
ACTION=-print

while [[ $# -gt 0 ]]; do
    case"$1" in
       -delete|-exec|-execdir|-fls|-fprint|-fprint0|-fprintf|-ok|-print|-okdir|-print0|-printf|-prune|-quit|-ls)
            ACTION=;;
    esac

    EXPR+=("$1")
    shift
done

if [[ ${#EXPR} -eq 0 ]]; then
    EXPR=(-true)
fi

exec -a"$(basename"$0")" find"${OPTIONS[@]}""${PATHS[@]}" -name .svn -type d -prune -o '('"${EXPR[@]}" ')' $ACTION

这个脚本的行为与普通的find命令相同,但它删除了.svn目录。否则,行为是相同的。

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
# svnfind -name 'messages.*' -exec grep -Iw uint {} +
./messages.cpp:            Log::verbose <<"Discarding out of date message: id" << uint(olderMessage.id)
./messages.cpp:    Log::verbose <<"Added to send queue:" << *message <<": id" << uint(preparedMessage->id)
./messages.cpp:                Log::error <<"Received message with invalid SHA-1 hash: id" << uint(incomingMessage.id)
./messages.cpp:            Log::verbose <<"Received" << *message <<": id" << uint(incomingMessage.id)
./messages.cpp:            Log::verbose <<"Sent message: id" << uint(preparedMessage->id)
./messages.cpp:        Log::verbose <<"Discarding unsent message: id" << uint(preparedMessage->id)
./messages.cpp:        for (uint i = 0; i < 10 && !_stopThreads; ++i) {
./virus/messages.cpp:void VsMessageProcessor::_progress(const string &fileName, uint scanCount)
./virus/messages.cpp:ProgressMessage::ProgressMessage(const string &fileName, uint scanCount)
./virus/messages.h:    void _progress(const std::string &fileName, uint scanCount);
./virus/messages.h:    ProgressMessage(const std::string &fileName, uint scanCount);
./virus/messages.h:    uint        _scanCount;


find . | grep -v \.svn


为什么不用grep来表达你的命令,这很容易理解:

1
your find command| grep -v '\.svn'


我只是想在Kaleb和其他人的帖子(详细介绍了find -prune选项、ackrepofind命令等的使用)中添加一个简单的替代选项,它特别适用于您在问题中描述的用法(以及任何其他类似用法):

  • 为了提高性能,您应该始终尝试使用find ... -exec grep ... +(感谢Kenji指出这一点)或find ... | xargs egrep ...(便携式)或find ... -print0 | xargs -0 egrep ...(GNU;用于包含空格的文件名)而不是find ... -exec grep ... \;

    find ... -exec ... +find | xargs表单不为每个文件分叉egrep,而是一次分叉一堆文件,从而加快了执行速度。

  • 当使用find | xargs形式时,您也可以使用grep轻松、快速地修剪.svn或任何目录或正则表达式,即find ... -print0 | grep -v '/\.svn' | xargs -0 egrep ...(当您需要快速而不必费心记住如何设置find-prune逻辑时很有用。)

    find | grep | xargs方法类似于gnu find-regex选项(见ghostdog74的帖子),但更轻便(也适用于gnu find不可用的平台)。


  • 在源代码存储库中,我通常只想对文本文件执行操作。

    第一行是所有文件,不包括cvs、svn和git存储库文件。

    第二行排除所有二进制文件。

    1
    2
    find . -not \( -name .svn -prune -o -name .git -prune -o -name CVS -prune \) -type f -print0 | \
    xargs -0 file -n | grep -v binary | cut -d":" -f1

    我使用find和-not-path选项。我对李子没有好运气。

    1
    find .  -name"*.groovy" -not -path"./target/*" -print

    将发现groovy文件不在目标目录路径中。


    注意如果你这样做了

    find . -type f -name 'messages.*'

    当整个表达式(-type f -name 'messages.*'为真时,则隐含-print,因为没有"动作"(如-exec)。

    但是,要停止下降到某些目录中,您应该使用与这些目录匹配的任何内容,然后使用-prune(用于停止下降到目录中);如:

    find . -type d -name '.svn' -prune

    对于.svn目录,此值为true,我们可以使用布尔短路,方法是:使用-o(or),之后只在第一部分为false时检查-o之后的内容,因此不是.svn目录。换句话说,以下内容:

    find . -type d -name '.svn' -prune -o -name 'message.*' -exec grep -Iw uint {}

    只评估不在.svn目录中的文件的-o,即-name 'message.*' -exec grep -Iw uint {}的权限。

    请注意,由于.svn可能始终是一个目录(而不是文件),并且在这种情况下,肯定与名称'message.*不匹配,因此您可以省略-type d并执行以下操作:

    find . -name '.svn' -prune -o -name 'message.*' -exec grep -Iw uint {}

    最后,请注意,如果省略了任何操作(-exec是一个操作),请这样说:

    find . -name '.svn' -prune -o -name 'message.*'

    然后,-print操作是隐含的,但将应用于整个表达式,包括-name '.svn' -prune -o部分,从而打印所有.svn目录以及"message.*"文件,这可能不是您想要的。因此,当以这种方式使用-prune时,应该在布尔表达式的右侧使用"action"。当该操作正在打印时,您必须显式地添加它,如下所示:

    find . -name '.svn' -prune -o -name 'message.*' -print


    wcfind是一个查找包装器脚本,用于自动删除.svn目录。


    试试findrepo,它是一个简单的find/grep包装,比ack快得多。在这种情况下,您可以使用它,例如:

    1
    findrepo uint 'messages.*'


    要解决此问题,只需使用以下查找条件:

    1
    find \( -name 'messages.*' ! -path"*/.svn/*" \) -exec grep -Iw uint {} +

    您可以像这样添加更多限制:

    1
    find \( -name 'messages.*' ! -path"*/.svn/*" ! -path"*/CVS/*" \) -exec grep -Iw uint {} +

    您可以在"Operators"的手册页部分中找到有关此的更多信息:http://unixhelp.ed.ac.uk/cgi/man-cgi?找到


    这在Unix提示下对我有效

    gfind . \( -not -wholename '*\.svn*' \) -type f -name 'messages.*'
    -exec grep -Iw uint {} +

    上面的命令将列出不带.svn的文件,并执行您提到的grep。


    我通常会再次通过grep输入输出,删除.svn,在我的使用中不会慢很多。典型示例:

    1
    find -name 'messages.*' -exec grep -Iw uint {} + | grep -Ev '.svn|.git|.anythingElseIwannaIgnore'

    1
    find . -type f -print0 | xargs -0 egrep messages. | grep -Ev '.svn|.git|.anythingElseIwannaIgnore'