Linux命令:如何只查找文本文件?

122

经过从谷歌上的一些搜索,我得到的答案是:

find my_folder -type f -exec grep -l "needle text" {} \; -exec file {} \; | grep text

这种方法非常不方便,输出了一些不必要的文本信息,例如 mime 类型信息。有更好的解决方案吗?我在同一个文件夹中有很多图片和其他二进制文件以及许多需要进行搜索的文本文件。

17个回答

216

我知道这是一个旧帖子,但我偶然发现它并想分享我的方法,我发现这是使用 find 查找非二进制文件的一种非常快速的方法:

find . -type f -exec grep -Iq . {} \; -print
-I选项告诉grep立即忽略二进制文件,.选项以及-q将使它立即匹配文本文件,从而使它非常快。如果您担心空格(感谢@lucas.werkmeister的提示!),可以将-print更改为-print0以进行管道传输到xargs -0或其他内容。
此外,第一个点仅适用于某些BSD版本的find(例如在OS X上),但如果您想将其放入别名或其他东西中,则始终将其放在那里不会有什么影响。 编辑:正如@ruslan正确指出的那样,-and可以省略,因为它已经被隐含了。

19
在Mac OS X上,我需要将它更改为find . -type f -exec grep -Il "" {} \; - Alec Jacobson
3
这个比peoro的回答更好,因为1.它实际回答了问题 2.它不会产生误报 3.它的性能更优秀。 - user123444555621
6
你也可以使用 find -type f -exec grep -Iq . {} \; -and -print,它的优点是保留了 find 中的文件;你可以将 -print 替换为另一个只对文本文件运行的 -exec。如果让 grep 打印文件名,则无法区分包含换行符的文件名。 - Lucas Werkmeister
1
@NathanS.Watson-Haigh 不应该,因为它应该立即匹配文本文件。您有可以分享的特定用例吗? - Cassie Dee
4
find . -type f -exec grep -Il . {} + 要快得多。缺点是无法像 @lucas.werkmeister 建议的那样通过另一个“-exec”进行扩展。 - Henning
显示剩余12条评论

11

10

为什么这样不方便?如果你需要经常使用它,而且不想每次都输入,只需要定义一个bash函数:

function findTextInAsciiFiles {
    # usage: findTextInAsciiFiles DIRECTORY NEEDLE_TEXT
    find "$1" -type f -exec grep -l "$2" {} \; -exec file {} \; | grep text
}

将其放入您的.bashrc中,然后只需运行:

findTextInAsciiFiles your_folder "needle text"

无论何时想要都可以。


编辑以反映OP的编辑:

如果你想剪掉mime信息,你可以添加一个进一步的步骤到管道中来过滤掉mime信息。这应该能够起到作用,只需取出:之前的内容即可:cut -d':' -f1

function findTextInAsciiFiles {
    # usage: findTextInAsciiFiles DIRECTORY NEEDLE_TEXT
    find "$1" -type f -exec grep -l "$2" {} \; -exec file {} \; | grep text | cut -d ':' -f1
}

@kavoir.com:是的。从file手册中可以看到:“用户依赖于知道目录中所有可读文件都打印了单词'text'。” - peoro
2
在使用grep命令之前,先搜索文本文件然后再进行过滤,这样不是更聪明一些吗? - user unknown
/proc/meminfo/proc/cpuinfo等是文本文件,但是file /proc/meminfo显示/proc/meminfo: empty。我想知道是否应该除了检测“text”之外还要测试“empty”,但不确定是否还有其他类型也会报告为空。 - Timo Kähkönen
为什么它不方便?——因为输出了不必要的文本。这个答案并没有解决问题。 - user123444555621
@Pumbaa80:嗯?OP后来添加了“并输出不需要的文本,例如MIME类型信息”的内容,修改了问题,因此这个答案也进行了修改,在管道中添加了cut以仅获取OP感兴趣的数据。我不明白你的观点... - peoro
显示剩余2条评论

5
find . -type f -print0 | xargs -0 file | grep -P text | cut -d: -f1 | xargs grep -Pil "search"

很遗憾,这不是空间节省的。将其放入bash脚本中会使它变得更容易一些。

这是空间安全的:

#!/bin/bash
#if [ ! "$1" ] ; then
    echo "Usage: $0 <search>";
    exit
fi

find . -type f -print0 \
  | xargs -0 file \
  | grep -P text \
  | cut -d: -f1 \
  | xargs -i% grep -Pil "$1" "%"

2
你的脚本存在一些问题:1. 如果一个二进制文件被命名为 text.bin 会怎样?2. 如果文件名包含 : 字符会怎样? - thkala

4
另一种方法是:
# find . |xargs file {} \; |grep "ASCII text"

如果您想要空文件:
#  find . |xargs file {} \; |egrep "ASCII text|empty"

2
我对histumness的答案有两个问题:
  • It only list text files. It does not actually search them as requested. To actually search, use

    find . -type f -exec grep -Iq . {} \; -and -print0 | xargs -0 grep "needle text"
    
  • It spawns a grep process for every file, which is very slow. A better solution is then

    find . -type f -print0 | xargs -0 grep -IZl . | xargs -0 grep "needle text"
    

    or simply

    find . -type f -print0 | xargs -0 grep -I "needle text"
    

    This only takes 0.2s compared to 4s for solution above (2.5GB data / 7700 files), i.e. 20x faster.

此外,没有人提到作为替代方案的ag(银色搜索器)ack-grep。如果其中一个可用,它们是更好的选择。请注意保留HTML标签。
ag -t "needle text"    # Much faster than ack
ack -t "needle text"   # or ack-grep

作为最后的提示,要注意假阳性(将二进制文件误认为是文本文件)。我曾经使用grep/ag/ack遇到过假阳性,所以最好先列出匹配的文件再编辑文件。

如果你想要速度,你就需要ugrep——它始终能够在你面前击败所有的基准测试。 - undefined
对于那些使用ack/ag的人来说,因为他们确实需要跳过在.gitignore中列出的所有无用文件,只需使用ugrep --ignore-files(默认使用.gitignore,但也可以传递其他文件以忽略文件的通配符列表)。当然,要忽略二进制文件,只需使用--ignore-binary(或-I)选项。为了提高速度,您可以使用ugrep-indexer,这是一个伴侣工具,它将预先索引目录中的文件以加快性能。 - undefined
@GwynethLlewelyn 我知道 ripgrep,但不知道 ugrep。谢谢你的建议。 - undefined

2
这是我完成它的方法...
1. 编写一个小脚本来测试文件是否为纯文本 istext:
#!/bin/bash
[[ "$(file -bi $1)" == *"file"* ]]

2. 如之前一样使用find函数。

find . -type f -exec istext {} \; -exec grep -nHi mystring {} \;

我猜你的意思是 == *"text"* ]] - user unknown
你可以使用匹配运算符=~"text"]]代替。 - user unknown

2
这个怎么样:
$ grep -rl "needle text" my_folder | tr '\n' '\0' | xargs -r -0 file | grep -e ':[^:]*text[^:]*$' | grep -v -e 'executable'

如果您想要文件名而不带文件类型,只需添加一个最终的sed过滤器即可。
$ grep -rl "needle text" my_folder | tr '\n' '\0' | xargs -r -0 file | grep -e ':[^:]*text[^:]*$' | grep -v -e 'executable' | sed 's|:[^:]*$||'

您可以通过在最后一个grep命令中添加更多的-e 'type'选项来过滤掉不需要的文件类型。
编辑:
如果您的xargs版本支持-d选项,则上述命令变得更加简单:
$ grep -rl "needle text" my_folder | xargs -d '\n' -r file | grep -e ':[^:]*text[^:]*$' | grep -v -e 'executable' | sed 's|:[^:]*$||'

我真是个傻瓜。没注意到递归grep。据我理解,尽管在许多应用中有些限制,但它实际上相当快速。给你点赞。 - Antti Rytsölä

2

以下是一份简化版的说明,为像我这样的初学者提供了更多的解释,帮助他们学习如何在一行中输入多个命令。

如果你要按步骤写出这个问题,它会像这样:

// For every file in this directory
// Check the filetype
// If it's an ASCII file, then print out the filename

为了实现这个目标,我们可以使用三个UNIX命令:findfilegrepfind会检查目录中的每个文件。 file会给出文件类型。在我们的例子中,我们要寻找“ASCII文本”的返回结果。 grep将在来自file的输出中查找关键字“ASCII”。
那么我们如何将它们串联在一起成为一条命令?有多种方法可以做到,但是我发现按照我们的伪代码顺序进行最有意义(尤其是对于像我这样的初学者)。 find ./ -exec file {} ";" | grep 'ASCII' 看起来很复杂,但是当我们把它分解开来时并不难理解: find ./ = 查看此目录中的每个文件。 find命令打印与“表达式”或路径之后的任何内容匹配的文件名,在我们的例子中是当前目录或./
最重要的是要明白,第一个部分之后的所有内容都将被评估为True或False。如果是True,则文件名将被打印出来。如果不是,则命令继续执行。 -exec = 这个标志是find命令中的一个选项,它允许我们使用其他命令的结果作为搜索表达式。这就像在函数内部调用函数。 file {} = 在find内部调用的命令。file命令返回字符串,告诉您文件的文件类型。通常会像这样:file mytextfile.txt。在我们的例子中,我们希望它使用find命令正在查看的任何文件,因此我们将大括号{}放入其中,充当空变量或参数。换句话说,我们只是要求系统输出目录中每个文件的字符串。 ";" = 这是find命令所必需的,并且是我们-exec命令的结尾标点符号。如果需要更多解释,请参阅“find”手册,运行man find| grep 'ASCII' = |是一个管道。管道获取左侧的输出,并将其用作右侧的输入。它采用find命令的输出(一个字符串,是单个文件的文件类型),并测试它是否包含字符串'ASCII'。如果是,则返回True。
现在,在find ./右侧的表达式将在grep命令返回True时返回True。完成。

1
点赞分享详细解释为什么解决方案有效,而不仅仅是解决方案本身。 - Richie Thomas
听听,我们需要更多像这个在SO上的详细答案!干得好。 - undefined

1
尽管这是一个老问题,但我认为下面的信息将增加答案的质量。
当忽略设置了可执行位的文件时,我只使用以下命令:
find . ! -perm -111

为了防止其递归进入其他目录:
find . -maxdepth 1 ! -perm -111

不需要使用管道混合多个命令,只需使用强大的普通 find 命令即可。
免责声明:这并不完全符合 OP 的要求,因为它没有检查文件是否是二进制的。例如,它将过滤掉 bash 脚本文件,它们本身是文本,但设置了可执行位。
话虽如此,我希望这对任何人都有用。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接