grep サブフォルダーファイルと一致するファイル名の表示

grep サブフォルダーファイルと一致するファイル名の表示

ユニークなリクエストがあります。フォルダ内に多数のサブフォルダがあり、サブフォルダ内に多数の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
file5.csvにfooがある場合、最終出力はfile5.csvとして提供されなければなりません。

答え1

grep一人ではできません。少なくともfindいくつかの異なるプログラムを使用する必要があります。

使い方の一つです牛に似た一種の栄養find、、、、、、、およびバージョン:stat​​​​sorttailcutxargsgrepsed

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=/\./=/='

1つ以上の.csvファイルを含む各ディレクトリの検索-execdirオプションは、そのディレクトリに変更され、各一致するファイル名のフルパスのNUL区切りリストを出力するシェルコマンドを実行します。それぞれは、修正タイムスタンプとタブがプレフィックスとして使用されます。

その後、リストは数字でソートされ、最後に変更されたファイル名を除くすべてのファイル名が削除され、tailタイムスタンプがcut出力から取得され、ファイル名がxargsrunにパイプされますgrep

最後に、sed出力をクリーンアップして/./文字列に含まれているアーティファクトを削除し、それを 。追加sまたはs)が良く見えます。$(pwd)/%nstat --printf//.//./


メモ:

  1. 必要に応じて、 find'-mindepth-maxdepth述語を使用して、findがサブディレクトリを再帰的に取得する方法を制御できます。

  2. NULで区切られた出力はここで使用または生成されないため、grepファイルsed名に改行文字が含まれている場合はパイプで使用することは「安全」ではありませんが、端末にファイル名のみを表示したい場合は問題ありません。他のプログラムに安全にパイプするには、-Zgrepと-zsedにオプションを追加します。これら 2 つの変更を使用すると、ファイル名のリストは最初から最後まで NUL で区切られます。

  3. 単一ディレクトリの一致するファイル名がコマンドライン長制限(ARG_MAX、Linuxでは約2 MB)を超えると、sh -c '...'そのディレクトリに対して複数回実行する必要があるため、この操作は正しく機能しないため、目的の順序付け結果が削除されます。ファイル名のリストを追加します。これは注目に値しますが、実際には問題にはなりません。

    同様に、stat --printfフルパスを含むように各ファイル名を拡張すると、正常にstat実行できなくなる可能性があります。これは問題になる可能性が高くなりますが、実際にはまだ可能性が低いです。 2MB ARG_MAXを超えるには、パスプレフィックスが非常に長いファイル名が多く必要です。

  4. これはしばしば、「装飾 - 並べ替え - 装飾解除」またはそれに似た非常に一般的な技術の例です。プログラマーは少なくともlispが始まって以来、長い間さまざまな言語でそれを使用してきました。この場合、findタイムスタンプに基づいてソートすることは不可能であるため、それを行うには、検索(装飾)の出力にタイムスタンプを追加してソートしてから、タイムスタンプを削除(装飾解除)する必要があります。


perl以下のコメントのいずれかで述べたように、この操作は「を通じて」行うこともできます。ファイル::検索そしてIO::圧縮解除::すべての圧縮解除基準寸法:

#!/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/

答え2

ファイルを含むサブディレクトリのセットが提供されます。

 % tree -tD --timefmt='%H:%M:%S'
.
├── [07:46:40]  SubfolderKEL
│   ├── [07:46:20]  File1
│   ├── [07:46:24]  File3
│   ├── [07:46:26]  File4
│   ├── [07:46:30]  File6
│   ├── [07:46:32]  File7
│   ├── [07:46:34]  File8
│   ├── [07:46:36]  File9
│   └── [08:05:32]  File11
├── [07:46:54]  SubfolderWB
│   ├── [07:46:38]  File10
│   ├── [07:46:48]  File15
│   ├── [07:46:52]  File17
│   └── [07:46:54]  File18
└── [07:46:58]  SubfolderMUM
    ├── [07:46:22]  File2
    ├── [07:46:28]  File5
    ├── [07:46:42]  File12
    ├── [07:46:44]  File13
    ├── [07:46:46]  File14
    ├── [07:46:50]  File16
    ├── [07:46:56]  File19
    └── [07:46:58]  File20

3 directories, 20 files

その後、zsh匿名関数でglob修飾子を使用して、各サブディレクトリから最新のファイル(修正時間ベース)を選択できます。

 % for d (Subfolder*(/)) (){ print -rC1 $@ } $d/*(om[1])
SubfolderKEL/File11
SubfolderMUM/File20
SubfolderWB/File18

同じ構造を使用してgrepコンテンツをインポートし、一致を含むファイル名を返すことができます。

 % for d (Subfolder*(/)) (){ grep -l foo -- $@ } $d/*(om[1])
SubfolderKEL/File11

答え3

作成時間(Unixでは保持されていません)ではなくファイルの変更時間に満足しているとし、GNUfindsort次のものを使用してくださいawk

#!/usr/bin/env bash

find . -type f -name '*.csv' -printf '%T@ %p\0' |
sort -srnz |
awk -v RS='\0' '
    ARGIND == 1 {
        match($0,"[^ ]+ ((.*)/[^/]+$)",a)
        if ( !seen[a[2]]++ ) {
            ARGV[ARGC++] = a[1]
        }
    }
    /foo/ {
        print FILENAME
        nextfile
    }
' -

答え4

以下は、zshシェルを使用して変数にpattern一致させる基本正規表現が含まれていると仮定します。

for dirpath in Subfolder*(/); do
    grep -l -e $pattern $dirpath/*.csv(.om[1])
done

このforループは、名前で始まる現在のディレクトリを繰り返しますSubfolder。各ディレクトリには、最近変更された一般ファイル(名前がパターンと一致する)*.csvが提供されますgrep。ユーティリティgrepは指定された正規表現と一致しようとし、一致する場合はファイル名(サブディレクトリ名を含む)を印刷します。

ここで使用される特殊機能は、zsh2つのグローバル修飾子(/)です(.om[1])。 1つ目は、前のパターンがディレクトリにのみ一致するようにし、2つ目は、パターンが通常のファイルにのみ一致するようにし、修正タイムスタンプに基づいてファイルを並べ替えることによって、ソートされた項目のうちの1つ目(つまり、最も最近に変更された一般ファイル)のみを返します。

この-lオプションをgrep使用すると、一致するファイルのパス名のみが出力されます。

関連情報