Shell-Script

grep 子文件夾文件並顯示匹配的文件名

  • June 1, 2022

我有一個獨特的要求。文件夾內有大量子文件夾,子文件夾內有大量 CSV 文件。看起來像下面

SubfolderWB
>File1.csv
>File2.csv

SubfolderMUM
>File3.csv
>File4.csv
>file5.csv

SubfolderKEL
>File6.csv
>File7.csv

現在在每個子文件夾中,我需要選擇最後一個文件(或創建的最新文件)並使用 grep 匹配關鍵字。如果關鍵字匹配,我需要文件名。

範例:我需要在所有子文件夾的 CSV 文件中查找 foo。

所以我需要選擇文件 cat SubfolderWB/File2.csv,SubfolderMUM/file5.csv ,SubfolderKEL/File7.csv | grep foo

如果 foo 存在於 file5.csv 中,它應該給我最終輸出為 file5.csv。

你不能grep一個人做這件事。您至少需要使用 ,find除此之外還有其他幾個程序。

這是使用GNU版本的find, stat, sort, tail, cut, xargs, grep, 和的一種方法sed

find . -type f -iname '*.csv' -execdir sh -c '
   stat --printf "%Y\t$(pwd)/%n\0" "$@" |
     sort -z -n |
     tail -z -n 1 |
     cut -z -f2- |
     xargs -0r grep -l foo' sh {} + | sed 's=/\./=/='

對於包含一個或多個 .csv 文件的每個目錄,find-execdir選項更改為該目錄並執行一個 shell 命令,該命令輸出一個以 NUL 分隔的每個匹配文件名的完整路徑列表,每個文件名都以其修改時間戳和一個選項卡為前綴.

然後該列表按數字排序,除了最近修改的文件名之外的所有文件名都被刪除(按tail),時間戳cut來自輸出,文件名通過管道傳輸到xargs執行grep

最後,sed用於清理輸出以刪除/./嵌入$(pwd)/%nstat --printf字元串中的工件並將其替換為 just /。這不是絕對必要的,因為路徑名在有或沒有路徑名的情況下完全相同/./(Unix 根本不關心路徑名的路徑部分中的額外/s 或s),但它看起來更好。./


筆記:

  1. 如果需要,您可以使用find’s-mindepth-maxdepth謂詞來控制 find 遞歸搜尋子目錄的方式。
  2. 此處既不使用grep也不sed產生 NUL 分隔的輸出,因此如果任何文件名包含換行符,則在管道中使用是不“安全的”,但如果您只想在終端中顯示文件名,則可以。為了安全地連接到其他程序,將-Z選項添加到 grep 和-zsed….通過這兩個更改,文件名列表將從頭到尾以 NUL 分隔。
  3. 如果任何單個目錄中的匹配文件名超過命令行長度限制(ARG_MAX,Linux 上約為 2MB),這將無法正常工作,因為它將必須sh -c '...'為該目錄執行多次,從而破壞所需的排序結果並拖尾文件名列表。這是值得注意的,但在實踐中不太可能成為問題。

同樣,stat --printf擴展每個文件名以包含其完整路徑,這可能會阻止stat成功執行….這更有可能是一個問題,但在實踐中仍然不太可能。它仍然需要有很多具有很長路徑前綴的文件名才能超過 2MB ARG_MAX。 4. 這是一個非常常用的技術範例,通常稱為“裝飾-排序-不裝飾”或類似技術。很長一段時間以來,各種語言的程序員都在使用它,至少在 lisp 還很新奇的時候。在這種情況下,find不能按時間戳排序,所以如果我們想這樣做,我們需要將時間戳添加到 find 的輸出(裝飾),然後對其進行排序,然後刪除時間戳(取消裝飾)。


正如我在下面的評論之一中提到的,這也可以通過perlFile ::FindIO::Uncompress::AnyUncompress模組來完成:

#!/usr/bin/perl

use File::Find;
use IO::Uncompress::AnyUncompress qw(anyuncompress $AnyUncompressError) ;
use Getopt::Std;
use strict;

my %files;   # hash-of-arrays to contain the filename with newest timestamp for each dir
my @matches; # array to contain filenames that contain the desired search pattern
my %opts;    # hash to contain command-line options

sub usage {
 print <<__EOF__;
$0 [-p 'search pattern'] [-f 'filename pattern'] [directory...]
-p and -f are required, and must have arguments.
directory defaults to current directory.
Example:
  $0 -p ABCD-713379 -f 'WB.*\.xml\.gz$' /data/inventory/ 
__EOF__
 exit 1
};

# Extremely primitive option processing and error checking.
usage unless getopts('p:f:', \%opts) && $opts{p} && $opts{f};

# default to current directory if not supplied.
@ARGV = qw(./) unless @ARGV;

# Find the newest filename in each subdirectory
find(\&wanted, @ARGV);

# OK, we should now have a %files hash where the keys are the
# directory names, and the values are an array containing a
# timestamp and the newest filename in that directory.
#
# Now "grep" each of those files by reading in each
# line and seeing if it contains the search pattern.
# IO::Uncompress::AnyUncompress ensures this works with
# compressed and uncompressed files.  Works with most common
# compression formats.
# The `map ...` extracts only the filenames from %files - see "perldoc -f map"
foreach my $f (map { $files{$_}[1] } keys %files) {
 my $z = IO::Uncompress::AnyUncompress->new($f) or
   warn "anyuncompress failed for '$f': $AnyUncompressError\n";

 while (my $line = $z->getline()) {
   if ($line =~ m/$opts{p}/i) { push @matches, $f ; last };
 };
};

# Output the list of matching filenames, separated by newlines.
print join("\n",@matches), "\n";
#print join("\0",@matches), "\0";  # alternatively, NUL-separated filenames

# "wanted()" subroutine used by File::Find to match files
sub wanted {
 # ignore directories, symlinks, etc and files that don't
 # match the filename pattern.
 return unless (-f && /$opts{f}/i);

 # Is this the first file we've seen in this dir? Is the current
 # file newer than the one we've already seen?
 # If either is true, store it in %files.
 my $t = (stat($File::Find::name))[9];
 if (!defined $files{$File::Find::dir} || $t > $files{$File::Find::dir}[0]) {
   $files{$File::Find::dir} = [ $t, $File::Find::name ]
 };
};

忽略註釋,這大約是 35 行程式碼。其中大部分是樣板。編寫註釋比編寫程式碼花費的時間更長,因為其中大部分只是從模組的手冊頁或我之前編寫的類似腳本中複製粘貼和編輯的。

執行它,例如./find-and-grep.pl -f '\.csv$' -p foo ./.

或者./find-and-grep.pl -p ABCD-713379 -f 'WB.*\.xml\.gz$' /data/inventory/

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