linux文件内容搜索

文件内容搜索

在linux下,搜索文件内容通常用grep。

grep的弊端一:编码影响搜索

grep一般用于内容搜索,它功能固然强大,但是在一些特殊的应用场景中会失效。比如被搜索的文件里面的编码是非UTF-8时,因为我们的系统通常默认编码是UTF-8,所以你的终端也一般是UTF-8编码,当你在终端输入如下搜索命令时:

grep -nr "编码" .

首先你的匹配模式的内容就是UTF-8编码的,这是由终端的默认编码决定的,通过:

thammer@exc:~/test/search$ echo -n "编码" | hexdump  -C
00000000  e7 bc 96 e7 a0 81                                 |......|
00000006

可以看到编码这两个字的编码值分别是e7bc96e7a081,也就是存储在文件里面的值就是这个。然后修改终端属性,让其默认编码改为GB2312,同样执行上面的命令:

thammer@exc:~/test/search$ echo -n "编码" | hexdump -C
00000000  b1 e0 c2 eb                                       |....|
00000004

同样是编码这两个字,它的值就变成了b1e0c2eb。因此使用grep搜索时,必然搜索不到,因为grep如果非正则比较时,使用的就是简单的值是否相等来判断。

有如下两个文件:

thammer@exc:~/test/search$ file *.txt
gb2312.txt: ISO-8859 text, with no line terminators
utf8.txt:   Unicode text, UTF-8 text

他们分别是GB2312和UTF-8编码,里面内容都包含编码两个汉字。使用grep搜索,匹配上哪个文件,完全由你的终端默认编码决定,因为上面已经解释清楚了:你的grep命令的匹配模式的字符串编码也是由终端决定。

grep弊端二:文件类型影响搜索

如果你要搜索的内容是保存在诸如PDF,DOCX,DOC这种类型的文件中,grep是无能为力的,因为这些文件不再是单纯的文本文件,而是特殊格式二进制文件。grep, egrep, fgrep, rgrep 这一系列的命令都是基于:匹配模式的文本行,重点是文本

ripgrep-all(rga)

为了解决这些问题,有人开发了一个极其强大的工具:ripgrep-all又称rga。项目地址在ripgrep-all。它可以在PDF,电子书,办公软件文档(docx,excel,ppt),压缩文件里面匹配内容,甚至是视频文件里面匹配字幕。rga是一个基于ripgrep(简称rg)的增强版命令行搜索工具。它的核心特点是能够搜索文件内部的内容,而不仅仅是纯文本文件

ripgrep-all核心设计

graph TD A[rga 主程序]; subgraph "核心协调引擎" A --> B[检查文件类型]; end subgraph "适配器层 (Adapter Layer)" B -- "文本文件(.txt, .md, .json...)" --> C[直接传递内容]; B -- "PDF 文件(.pdf)" --> D[poppler-utils(pdftotext)]; B -- "Office 文档(.docx, .xlsx, .pptx)" --> E[Apache Tika / pandoc]; B -- "压缩包(.zip, .tar.gz)" --> F[递归解压后处理]; B -- "其他格式(e.g., .epub, 图片元数据)" --> G[其他适配器(如 exiftoo)]; end subgraph "ripgrep 核心搜索引擎" H[ripgrep(rg 引擎)] --> I[正则表达式匹配]; end C --> H; D --> H; E --> H; F --> B; G --> H; I --> J((最终结果));

可以看到rga是分析文件类型,然后调用第三方工具将文件转换为文本形式,最后交给ripgrep进行匹配。总的来说rga主要是协调第三方工具进行模式匹配。它还支持自定义适配器,所谓适配器就是指:设置哪一类文件通过哪种工具转为文本。

ripgrep-all的安装

核心协调引擎

首先需要安装其核心协调引擎,貌似现在的linux发行版没有其安装包,需要从它的代码仓库下载,选择适合你设备的版本,这里我选择:ripgrep_all-v0.10.10-x86_64-unknown-linux-musl.tar.gz,

tar xfz ripgrep_all-v0.10.10-x86_64-unknown-linux-musl.tar.gz
cd ripgrep_all-v0.10.10-x86_64-unknown-linux-musl
ls

里面包含4个可执行文件:

rga  rga-fzf  rga-fzf-open  rga-preproc
  • rga就是完整程序,通常就用它
  • rga-fzfrga-fzf-open和模糊搜索相关
  • rga-preproc是预处理程序,通常用来调试分析,它相当于上图中核心协调引擎 + 适配器层,起到文本化的作用。

安装至系统:

sudo cp rga* /usr/local/bin

安装文本化工具

文本化工具就是一些可以提取诸如PDF,视频文件,压缩文件,DOCX、XLSX,PPTX等文件的文本内容的一些第三方工具。

sudo apt install pandoc poppler-utils ffmpeg unzip
  • pandoc是转换Microsoft Word docxMicrosoft PowerPoint,电子书等格式文件到文本的工具,它实际上非常强大,支持很多种格式的文档转换,Pandoc a universal document converter

  • popler-utils提供一个工具集,其中里面的pdftotext就是可以从pdf中提取文本内容。

  • ffmpeg主要是提取视频、音频文件的元数据和字幕。

  • unzip解压缩后在根据文件类型递归处理。

实际你可以自己写一个处理某种文件的工具,然后编辑rga的配置文件,自定义自己的适配器,这个下面再介绍。

安装核心搜索引擎

rga的核心搜索引擎就是ripgrep(又称rg)。它负责在上级适配器层转换出来的文本中进行搜索。

sudo apt install ripgrep

rga的使用

rga的语法格式为:rga [选项] <模式> [路径]。它默认对当前目录进行递归搜索,并且会默认忽略.gitignore、隐藏文件等。它即支持简单字符串匹配,也支持正则表达式匹配。rga的部分选项实际是会传递给rg使用。

基础搜索

如果不指定路径,默认就是当前目录,并且是递归搜索。

  • 当前目录搜索
rga "test"
  • 指定路径搜索
rga "test" /home/thammer/misc
  • 忽略大小写
rga -i "test"

这点和grep一样。

  • 区分大小写
rga -s "test"

-s是默认选项,通常省略。

  • 智能大小写
rga -S “test”

-S,--smart-case此参数表示当模式为全小写时,不区分大小写,否则区分大小写。

  • 反转匹配
rga -v "test"

正常情况下输出的是匹配模式的行,-v选项反过来了,是输出不匹配模式的行。和grep一样。

  • 仅输出匹配部分
rga -o "test"

正常情况下,模式匹配后,输出的是整行,但是如果指定了-o,就只输出匹配部分,和grep一样。

  • 输出行号
rga -n "test"

这种一般只对文本类型文件才又意义,和grep一样。

  • 多模式匹配
rga "(abc|def)"

多模式通过小括号包裹,各模式之间用|或操作符连接,表示匹配任意一个模式。

高阶搜索

  • 类型过滤

如果希望只搜索某些类型文件,只需要通过-t TYPE或者--type=TYPE来指定文件类型。相反使用-T TYPE或者--type-not=TYPE来跳过指定类型文件。

那怎么知道TYPE有哪些值呢?通过:

rga --type-list
...
...
bzip2: *.bz2, *.tbz2
c: *.[chH], *.[chH].in, *.cats
...
...

可以查看支持哪些类型。它的输出每行表示一种类型,以冒号分隔,前面表示TYPE,后面表示文件名匹配规则。通常是后缀,也可以是完整的文件名。

-t或者-T支持设置多个类型

rga -t txt -t c "hello"
  • 正则表达式模式匹配
# ip地址匹配
rga "\b(?:[0-9]{1,3}\.){3}[0-9]{1,3}\b" test.txt
# 邮箱地址匹配
rga "[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}" test.txt
  • 仅输出匹配文件路径
rga -l "hello"

选项太多不一一列举

自定义适配器

在默认配置中,我发现无法搜索.doc文档,只能搜索.docx.doc是早期的文档格式,文本化工具pandoc不支持.doc格式,支持.doc的是catdoc。可以查看默认的一些适配器:

rga --rga-list-adapters
...
...
 - **pandoc**
     Uses pandoc to convert binary/unreadable text documents to plain markdown-like text
     Runs: pandoc --from=$input_file_extension --to=plain --wrap=none --markdown-headings=atx  
     Extensions: .epub, .odt, .docx, .fb2, .ipynb, .html, .htm  
     Mime Types:
...

可以看到确实不支持.doc格式。需要注意rga的协调引擎在判断文件类型选择合适的文本化工具处理时,默认通过后缀来进行文件类型识别,这是为了处理效率考虑,但是不严谨,也可以指定--rga-accurate选项,通过MIME类型进行更精确但更慢的匹配。它采用类似file命令的-i选项方式读取文件前8KiB内容进行识别。

rga的默认配置文件在$HOME/.config/ripgrep-all/config.jsonc文件中,如何知道怎么配置呢?rga提供了一个选项可以输出它的配置文件语法:--rga-print-config-schema选项。以自定义适配器配置为例:

    "CustomAdapterConfig": {
      "type": "object", /*配置的类型为json对象*/
      "required": [     /*强制有的属性*/
        "args",
        "binary",
        "description",
        "extensions",
        "name",
        "version"
      ],
      "properties": {  /*属性的描述信息*/
        "name": {
          "description": "The unique identifier and name of this adapter.\n\nMust only include a-z, 0-9, _.",
          "type": "string"
        },
        "description": {
          "description": "The description of this adapter shown in help.",
          "type": "string"
        },
        "disabled_by_default": {
          "description": "If true, the adapter will be disabled by default.",
          "type": [
            "boolean",
            "null"
          ]
        },
        "version": {
          "description": "Version identifier used to key cache entries.\n\nChange this if the configuration or program changes.",
          "type": "integer",
          "format": "int32"
        },
        "extensions": {
          "description": "The file extensions this adapter supports, for example `[\"epub\", \"mobi\"]`.",
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "mimetypes": {
          "description": "If not null and `--rga-accurate` is enabled, mimetype matching is used instead of file name matching.",
          "type": [
            "array",
            "null"
          ],
          "items": {
            "type": "string"
          }
        },
        "match_only_by_mime": {
          "description": "If `--rga-accurate`, only match by mime types and ignore extensions completely.",
          "type": [
            "boolean",
            "null"
          ]
        },
        "binary": {
          "description": "The name or path of the binary to run.",
          "type": "string"
        },
        "args": {
          "description": "The arguments to run the program with. Placeholders: - `$input_file_extension`: the file extension (without dot). e.g. foo.tar.gz -> gz - `$input_file_stem`: the file name without the last extension. e.g. foo.tar.gz -> foo.tar - `$input_virtual_path`: the full input file path. Note that this path may not actually exist on disk because it is the result of another adapter.\n\nstdin of the program will be connected to the input file, and stdout is assumed to be the converted file",
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "output_path_hint": {
          "description": "The output path hint. The placeholders are the same as for `.args`\n\nIf not set, defaults to `\"${input_virtual_path}.txt\"`.\n\nSetting this is useful if the output format is not plain text (.txt) but instead some other format that should be passed to another adapter",
          "type": [
            "string",
            "null"
          ]
        }
      }
    }

现在要添加一个处理.doc文件的适配器,首先要安装将.doc文本化的工具catdoc

sudo apt install catdoc

按照上述的语法说明,为.doc文件添加适配器,在$HOME/.config/ripgrep-all/config.jsonc文件中加入如下内容

{
    "$schema": "./config.v1.schema.json",
    "custom_adapters": [
    // See https://github.com/phiresky/ripgrep-all/wiki for more information
    // to verify if your custom adapters are picked up correctly, run `rga --rga-list-adapters`
    {   
      "name": "catdoc",
      "description": "Uses catdoc to extract plain text from .doc files",
      "version": 1,
      "extensions": ["doc"],
      "mimetypes":["application/msword"],
      "binary": "catdoc",
      "args": ["$input_virtual_path"]
    }                                                      
  ]
}

多编码搜索

在进行搜索时,通常你也不知道实际文件里面可能是什么编码,虽然rga支持-E 编码,--encoding=编码但是逐个尝试始终有点麻烦,最好自动处理。一个文件如果可能是多种编码中的一种,通常它本身就是文本文件,于是可以直接先通过file命令识别到mime类型为text/plain的文件,然后再获取其编码,最后通过rga命令的-E选项指定编码即可。只要通过一个wrapper脚本,对rga命令wrapper一下就可以了。脚本取名为rga-auto,保存在/usr/local/bin下:

#!/bin/bash
# rga 自动编码处理脚本 - 智能检测文本文件编码
# 使用方法:rga-auto "关键词" 文件或目录

RGA_BIN="/usr/local/bin/rga"

# 从参数中提取文件路径(最后一个参数通常是文件/目录路径)
FILE_PATH=$(echo "$*" | awk '{print $NF}')

# 如果最后一个参数不是文件路径(可能是选项),尝试其他方式
if [ ! -e "$FILE_PATH" ] && [ -n "$2" ]; then
    # 尝试倒数第二个参数
    FILE_PATH="$2"
fi

# 如果文件/目录存在,进行智能判断
if [ -e "$FILE_PATH" ]; then
    # 1. 使用 file 命令快速判断文件类型
    FILE_TYPE=$(file -b --mime-type "$FILE_PATH" 2>/dev/null)

    # 2. 排除非文本文件(pdf, docx, 视频、图片等)
    case "$FILE_TYPE" in
        text/*|application/x-sh*|application/x-shellscript*)
            # 是文本文件,继续处理
            ;;
        *)
            # 非文本文件(pdf, docx, 视频等),直接调用 rga,不处理编码
            exec "$RGA_BIN" "$@"
            ;;
    esac

    # 3. 对于文本文件,使用 file -i 查看编码信息
    if [ -f "$FILE_PATH" ]; then
        CHARSET=$(file -i "$FILE_PATH" 2>/dev/null | grep -oP 'charset=\K[^;]+')

        # 4. 根据编码信息选择 rga 参数
        case "$CHARSET" in
            "utf-8"|"us-ascii"|"")
                # UTF-8 或 ASCII,直接调用 rga
                exec "$RGA_BIN" "$@"
                ;;
            "gb2312"|"gbk"|"gb18030"|"iso-8859-1")
                # 可能是中文编码,使用 enca 精确检测
                ENCODING=$(enca -L zh_CN -i "$FILE_PATH" 2>/dev/null)
                case "$ENCODING" in
                    "UTF-8"|"7-bit ASCII"|"")
                        exec "$RGA_BIN" "$@"
                        ;;
                    "GB2312"|"Simplified Chinese National Standard; GB2312")
                        exec "$RGA_BIN" --encoding GB2312 "$@"
                        ;;
                    "GBK")
                        exec "$RGA_BIN" --encoding GBK "$@"
                        ;;
                    "GB18030")
                        exec "$RGA_BIN" --encoding GB18030 "$@"
                        ;;
                    *)
                        # 未知编码,尝试常见中文编码
                        exec "$RGA_BIN" --encoding GB2312 "$@" 2>/dev/null || \
                             "$RGA_BIN" --encoding GBK "$@" 2>/dev/null || \
                             "$RGA_BIN" --encoding GB18030 "$@" 2>/dev/null || \
                             "$RGA_BIN" "$@"
                        ;;
                esac
                ;;
            *)
                # 其他编码,直接调用 rga(让 rga 自己处理)
                exec "$RGA_BIN" "$@"
                ;;
        esac
    else
        # 是目录,直接调用 rga(目录搜索时让 rga 自己处理每个文件)
        exec "$RGA_BIN" "$@"
    fi
else
    # 文件/目录不存在,直接调用 rga
    exec "$RGA_BIN" "$@"
fi

如果需要搜索的内容可能是某种编码时,但是又无法预先知道,则可以通过这个wrapper脚本搜索。

posted @ 2025-11-21 11:07  thammer  阅读(11)  评论(0)    收藏  举报