Linux

Linux 僅在給定的一組文件中搜尋字元串

  • December 14, 2020

一個目錄中有幾個文件。我正在嘗試搜尋並找到所有以給定字元串結尾的字元串。我不想搜尋目錄中存在的所有文件,而只想搜尋給定的一組文件名。最後,輸出應該是每個文件名以及在該文件中找到的帶有分號分隔符的字元串。

簡化的測試案例是:目錄下有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 腳本

請問有什麼幫助嗎?

我假設您已經修復了評論中討論的 DOS 樣式的行尾,並且searchFiles.txt實際上不包含空行。

-name測試find只採用一種文件名模式。模式可能包含 shell glob 字元,但應保護這些字元免受 shell 過早生成文件名。您可以使用邏輯 OR 連接多個此類測試,-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為多個較小的文件來解決此限制。

您應該能夠使用 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.

引用自:https://unix.stackexchange.com/questions/624269