독특한 요청이 있습니다. 폴더 내에 많은 수의 하위 폴더가 있고 하위 폴더 내에 많은 수의 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
, 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 파일이 포함된 각 디렉터리에 대해 찾기 -execdir
옵션은 해당 디렉터리로 변경되고 각 일치하는 파일 이름의 전체 경로에 대한 NUL로 구분된 목록을 출력하는 셸 명령을 실행합니다. 각각은 수정 타임스탬프와 탭이 접두사로 사용됩니다.
그런 다음 목록은 숫자순으로 정렬되고 가장 최근에 수정된 파일 이름을 제외한 모든 파일 이름은 제거되고( 에 의해 tail
) 타임스탬프는 cut
출력에서 가져오고 파일 이름은 xargs
run 으로 파이프됩니다 grep
.
마지막으로 sed
출력을 정리하여 /./
문자열에 포함된 아티팩트를 제거하고 이를 . 추가 s 또는 s)가 더 좋아 보입니다.$(pwd)/%n
stat --printf
/
/./
/
./
노트:
원하는 경우
find
'-mindepth
및-maxdepth
조건자를 사용하여 find가 하위 디렉토리를 재귀적으로 검색하는 방법을 제어할 수 있습니다.NUL로 구분된 출력은 여기에서 사용되거나 생성되지
grep
않으므로sed
파일 이름에 개행 문자가 포함된 경우 파이프에서 사용하는 것이 "안전"하지 않지만 터미널에 파일 이름만 표시하려는 경우에는 괜찮습니다. 다른 프로그램으로 안전하게 파이프하려면-Z
grep 및-z
sed에 옵션을 추가하십시오. 이 두 가지 변경 사항을 사용하면 파일 이름 목록이 처음부터 끝까지 NUL로 구분됩니다.단일 디렉터리의 일치하는 파일 이름이 명령줄 길이 제한(ARG_MAX, Linux에서는 약 2MB)을 초과하는 경우
sh -c '...'
해당 디렉터리에 대해 여러 번 실행해야 하므로 이 작업이 제대로 작동하지 않아 원하는 순서 지정 결과가 삭제됩니다. 파일 이름 목록을 추가합니다. 이는 주목할 가치가 있지만 실제로는 문제가 되지 않을 것입니다.마찬가지로,
stat --printf
전체 경로를 포함하도록 각 파일 이름을 확장하면stat
성공적인 실행을 방해할 수 있습니다. 이는 문제가 될 가능성이 더 높지만 실제로는 여전히 가능성이 낮습니다. 2MB ARG_MAX를 초과하려면 경로 접두사가 매우 긴 파일 이름이 많이 필요합니다.이것은 종종 "장식-정렬-장식 취소" 또는 이와 유사한 매우 일반적인 기술의 예입니다. 프로그래머들은 적어도 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 find
및 sort
다음 을 사용하세요 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
사용하면 일치하는 파일의 경로 이름만 출력됩니다.