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/

おすすめ記事