Linux 仅在给定的文件集中搜索字符串

Linux 仅在给定的文件集中搜索字符串

一个目录中有多个文件。我正在尝试搜索并找到所有以给定字符串结尾的字符串。我只想搜索给定的一组文件名,而不是搜索目录中存在的所有文件。最后,输出应该是每个文件名以及该文件中找到的带有分号分隔符的字符串出现次数。

简化的测试用例是:目录中有5个文件:

file.a.txt
file.b.txt
file.c.txt
file.d.txt
file.e.txt

还有一个名为 的文件searchFiles.txt包含上面列表中的前 3 个文件名。所以我想仅在 中列出的文件名中搜索字符串searchFiles.txt

我努力了:

for i in $(cat searchFiles.txt); do grep -o '[^ ]*_XYZ' /dev/null $i ; done | awk -F: '{a[$1]=a[$1]";"$2;} END{for (x in a) print x ":" substr(a[x],2);}'

但输出说

: No such file or directory
: No such file or directory
file.c.txt:FOUND1_XYZ;FOUND2_XYZ

因此,不知何故,它只能搜索 searchFiles.txt 中给出的最后一个文件名,但无法找到其他初始文件,从而引发错误“没有这样的文件或目录”

我期望的输出是:

file.a.txt:FOUNDSTR_XYZ
file.b.txt:FOUNDSTR1_XYZ;FOUNDSTR2_XYZ;FOUNDSTR3_XYZ
file.c.txt:FOUND1_XYZ;FOUND2_XYZ

我还试图查找“find”命令的“-name”标志是否有帮助,但无法完全了解如何准确地从此处提供 searchFiles.txt 中的文件列表。下面的尝试失败了。

find . -type f -name `cat searchFiles.txt` -exec grep -o '[^ ]*_XYZ' /dev/null {} \;

还:

  • 一个目录中最多可以有几千个文件,searchFiles.txt 中的搜索文件名也可以有几百个文件名。

  • 文件名可以是任何名称,并且不遵循任何模式。

  • searchFiles.txt 中提供的文件名可以是部分名称,例如 a.txt,而不是 file.a.txt,这意味着文件名“file”的初始静态部分。可能存在也可能不存在于 searchFiles.txt 中。

  • 最好寻找单行命令而不是 shell 脚本

请问对此有什么帮助吗?

答案1

您应该能够使用 GNU 完成所有事情awk,例如:

find . -type f -print0 |
  gawk '
    step == 1 {files[$0]; next} # record file names in "files" array
    step == 2 {
      # determine which files to look into (added to ARGV array for
      # processing in step 3)
      if ($NF in files) ARGV[ARGC++] = $0; next
    }
    NF {
      # record all matches (here in fields matched by FPAT)
      $1 = $1 # force a rebuild of $0 joining fields with OFS
      matches[FILENAME] = matches[FILENAME] \
                          (matches[FILENAME] == "" ? "" : OFS) \
                          $0
    }
    END {
      for (file in matches)
        print file ": " matches[file]
    }' step=1 searchFiles.txt \
       step=2 RS='\0' FS=/ - \
       step=3 RS='\n' FPAT='[^ ]*_XYZ' OFS=';'

上面,文件名与存储在searchFiles.txt.如果该文件的行是后缀列表,您可以构建一个正则表达式而不是关联数组:

find . -type f -print0 |
  gawk '
    step == 1 {
      gsub(/[][^$*()+{}?\\.|]/, "\\\\&") # escape regexp operators
      regex = regex sep $0; sep = "|"
      next
    }
    step == 2 {
      # determine which files to look into (added to ARGV array for
      # processing in step 3)
      if ($NF ~ ("(" regex ")$")) ARGV[ARGC++] = $0; next
    }
    NF {
      # record all matches (here in fields matched by FPAT)
      $1 = $1 # force a rebuild of $0 joining fields with OFS
      matches[FILENAME] = matches[FILENAME] \
                          (matches[FILENAME] == "" ? "" : OFS) \
                          $0
    }
    END {
      for (file in matches)
        print file ": " matches[file]
    }' step=1 searchFiles.txt \
       step=2 RS='\0' FS=/ - \
       step=3 RS='\n' FPAT='[^ ]*_XYZ' OFS=';'

如果需要对其进行混淆,可以将其放在一行中:

find . -type f -print0|gawk '!s{gsub(/[][^$*()+{}?\\.|]/,"\\\\&");r=r p $0;p="|";next};s==2{if($NF~("("r")$"))ARGV[ARGC++]=$0;next};NF{$1=$1;m[FILENAME]=m[FILENAME](m[FILENAME]==""?"":OFS)$0};END{for(f in m)print f":"m[f]}' searchFiles.txt s=2 RS=\\0 FS=/ - s=3 RS=\\n FPAT='[^ ]*_XYZ' OFS=\;

它们不假设文件名和内容可能包含哪些字符,除非它们必须是语言环境中的有效字符。后缀不能有换行符,但这是由searchFiles.txt.

答案2

我假设您已经修复了注释中讨论的 DOS 风格的行结尾,并且searchFiles.txt实际上并不包含空行。

-name的测试仅find采用一种文件名模式。模式可能包含 shell glob 字符,但应保护这些字符,以免 shell 过早生成文件名。您可以使用逻辑或连接多个此类测试,-o但需要注意运算符优先级。

如果您的 shell 支持数组,您可以执行以下操作的一种方法(我bash在这里使用,但在其他 shell 中应该可以执行类似的过程):

files=( -false )
while IFS= read -r f || [ -n "$f" ]; do files+=( -o -name "*$f"); done < searchFiles.txt

这应该会导致${files[@]}扩展到交替

-false -o -name *file.a.txt -o -name *file.b.txt -o -name *file.c.txt -o -name *file.d.txt -o -name *file.e.txt

然后你可以在你的find命令中使用它,例如

find . \( "${files[@]}" \) -exec grep -Ho '[^ ]*_XYZ' {} +

(我省略了虚拟文件,/dev/null转而添加-H选项)。如果文件数量searchFiles.txt太大,此方法可能会失败,因为ARG_MAX限制而失败。您可以通过拆分searchFiles.txt为多个较小的文件来解决此限制。

答案3

grep -f您可以使用要包含的名称(允许部分匹配)通过文本文件将文件名过滤到目录中。然后对这些文件进行大量的grep搜索模式,最后用一个小的awk.

使用 GNU bash

grep -Ff filenames.txt <(printf '%s\n' *) |
    xargs -d '\n' grep -oH '[^[:space:]]*_XYZ$' | awk -F: '
        {f[$1] = f[$1] ? f[$1] ";" $2 : $0}
        END {for (x in f) print f[x]}'

一些假设(问题尚未全部明确):

  • 您的文件名很方便,没有换行符,没有冒号(用于输出grep)。空格已处理。
  • 那里没有匹配的子目录,否则第二个grep会抛出一条消息,但会返回结果。
  • 第二个grep在行尾查找模式。如果你想匹配单词结尾,你可以修改它。
  • -Hforgrep用于一个文件的极端情况,将文件名打印到输出中(当有两个或更多文件时,这是默认设置)

相关内容