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, sort, tail, cut, xargsgrep버전 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 파일이 포함된 각 디렉터리에 대해 찾기 -execdir옵션은 해당 디렉터리로 변경되고 각 일치하는 파일 이름의 전체 경로에 대한 NUL로 구분된 목록을 출력하는 셸 명령을 실행합니다. 각각은 수정 타임스탬프와 탭이 접두사로 사용됩니다.

그런 다음 목록은 숫자순으로 정렬되고 가장 최근에 수정된 파일 이름을 제외한 모든 파일 이름은 제거되고( 에 의해 tail) 타임스탬프는 cut출력에서 ​​가져오고 파일 이름은 xargsrun 으로 파이프됩니다 grep.

마지막으로 sed출력을 정리하여 /./문자열에 포함된 아티팩트를 제거하고 이를 . 추가 s 또는 s)가 더 좋아 보입니다.$(pwd)/%nstat --printf//.//./


노트:

  1. 원하는 경우 find' -mindepth-maxdepth조건자를 사용하여 find가 하위 디렉토리를 재귀적으로 검색하는 방법을 제어할 수 있습니다.

  2. NUL로 구분된 출력은 여기에서 사용되거나 생성되지 grep않으므로 sed파일 이름에 개행 문자가 포함된 경우 파이프에서 사용하는 것이 "안전"하지 않지만 터미널에 파일 이름만 표시하려는 경우에는 괜찮습니다. 다른 프로그램으로 안전하게 파이프하려면 -Zgrep 및 -zsed에 옵션을 추가하십시오. 이 두 가지 변경 사항을 사용하면 파일 이름 목록이 처음부터 끝까지 NUL로 구분됩니다.

  3. 단일 디렉터리의 일치하는 파일 이름이 명령줄 길이 제한(ARG_MAX, Linux에서는 약 2MB)을 초과하는 경우 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에서는 유지하지 않음)이 아닌 파일 수정 시간에 만족한다고 가정하고 GNU findsort다음 을 사용하세요 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는 주어진 정규식과 일치하도록 시도하고 일치하는 경우 파일 이름(하위 디렉터리 이름 포함)을 인쇄합니다.

여기에 사용된 특수 기능은 zsh두 개의 전역 한정자 (/)와 입니다 (.om[1]). 첫 번째는 이전 패턴이 디렉터리에만 일치하도록 하고, 두 번째는 패턴이 일반 파일에만 일치하도록 만들고 수정 타임스탬프를 기준으로 파일을 정렬하여 정렬된 항목 중 첫 번째 항목(즉, 가장 최근에 수정된 일반 파일)만 반환합니다.

-l옵션을 grep사용하면 일치하는 파일의 경로 이름만 출력됩니다.

관련 정보