한 디렉터리에 여러 개의 파일이 있습니다. 주어진 문자열로 끝나는 모든 문자열을 검색하고 찾으려고 합니다. 디렉토리에 있는 모든 파일이 아닌 특정 파일 이름 세트만 검색하고 싶습니다. 마지막으로 출력은 각 파일 이름과 해당 파일에서 발견된 세미콜론으로 구분된 문자열의 발생 횟수여야 합니다.
단순화된 테스트 사례는 다음과 같습니다. 디렉터리에 5개의 파일이 있습니다.
file.a.txt
file.b.txt
file.c.txt
file.d.txt
file.e.txt
searchFiles.txt
위 목록의 처음 3개 파일 이름을 포함하는 파일도 있습니다 . 그래서 에 나열된 파일 이름에서만 문자열을 검색하고 싶습니다 searchFiles.txt
.
나는 시도했다:
for i in $(cat searchFiles.txt); do grep -o '[^ ]*_XYZ' /dev/null $i ; done | awk -F: '{a[$1]=a[$1]";"$2;} END{for (x in a) print x ":" substr(a[x],2);}'
그러나 출력은 다음과 같이 말합니다.
: No such file or directory
: No such file or directory
file.c.txt:FOUND1_XYZ;FOUND2_XYZ
따라서 어떻게든 searchFiles.txt에 지정된 마지막 파일 이름만 검색할 수 있지만 다른 초기 파일은 찾을 수 없으므로 "해당 파일 또는 디렉터리가 없습니다."라는 오류가 발생합니다.
내 예상 결과는 다음과 같습니다
file.a.txt:FOUNDSTR_XYZ
file.b.txt:FOUNDSTR1_XYZ;FOUNDSTR2_XYZ;FOUNDSTR3_XYZ
file.c.txt:FOUND1_XYZ;FOUND2_XYZ
또한 "find" 명령의 "-name" 플래그가 도움이 되는지 알아보려고 했지만 여기에서는 searchFiles.txt의 파일 목록을 정확하게 제공하는 방법을 잘 알 수 없습니다. 다음 시도가 실패했습니다.
find . -type f -name `cat searchFiles.txt` -exec grep -o '[^ ]*_XYZ' /dev/null {} \;
반품:
디렉터리에는 최대 수천 개의 파일이 있을 수 있으며, searchFiles.txt에서 검색되는 파일 이름은 수백 개가 될 수 있습니다.
파일 이름은 무엇이든 될 수 있으며 어떠한 패턴도 따르지 않습니다.
searchFiles.txt에 제공되는 파일 이름은 파일 이름 "file"의 초기 정적 부분을 의미하는 file.a.txt 대신 a.txt와 같은 부분 이름일 수 있습니다. searchFiles.txt에 존재할 수도 있고 존재하지 않을 수도 있습니다.
쉘 스크립트보다는 한 줄 명령을 찾는 것이 더 좋습니다.
이에 대한 도움이 필요하신가요?
답변1
awk
GNU를 사용하여 다음과 같은 모든 작업을 수행할 수 있어야 합니다 .
find . -type f -print0 |
gawk '
step == 1 {files[$0]; next} # record file names in "files" array
step == 2 {
# determine which files to look into (added to ARGV array for
# processing in step 3)
if ($NF in files) ARGV[ARGC++] = $0; next
}
NF {
# record all matches (here in fields matched by FPAT)
$1 = $1 # force a rebuild of $0 joining fields with OFS
matches[FILENAME] = matches[FILENAME] \
(matches[FILENAME] == "" ? "" : OFS) \
$0
}
END {
for (file in matches)
print file ": " matches[file]
}' step=1 searchFiles.txt \
step=2 RS='\0' FS=/ - \
step=3 RS='\n' FPAT='[^ ]*_XYZ' OFS=';'
위에서 파일 이름은 에 저장됩니다 searchFiles.txt
. 파일의 줄이 접미사 목록인 경우 연관 배열 대신 정규 표현식을 작성할 수 있습니다.
find . -type f -print0 |
gawk '
step == 1 {
gsub(/[][^$*()+{}?\\.|]/, "\\\\&") # escape regexp operators
regex = regex sep $0; sep = "|"
next
}
step == 2 {
# determine which files to look into (added to ARGV array for
# processing in step 3)
if ($NF ~ ("(" regex ")$")) ARGV[ARGC++] = $0; next
}
NF {
# record all matches (here in fields matched by FPAT)
$1 = $1 # force a rebuild of $0 joining fields with OFS
matches[FILENAME] = matches[FILENAME] \
(matches[FILENAME] == "" ? "" : OFS) \
$0
}
END {
for (file in matches)
print file ": " matches[file]
}' step=1 searchFiles.txt \
step=2 RS='\0' FS=/ - \
step=3 RS='\n' FPAT='[^ ]*_XYZ' OFS=';'
난독화해야 하는 경우 한 줄에 입력할 수 있습니다.
find . -type f -print0|gawk '!s{gsub(/[][^$*()+{}?\\.|]/,"\\\\&");r=r p $0;p="|";next};s==2{if($NF~("("r")$"))ARGV[ARGC++]=$0;next};NF{$1=$1;m[FILENAME]=m[FILENAME](m[FILENAME]==""?"":OFS)$0};END{for(f in m)print f":"m[f]}' searchFiles.txt s=2 RS=\\0 FS=/ - s=3 RS=\\n FPAT='[^ ]*_XYZ' OFS=\;
파일 이름과 내용에 포함될 수 있는 문자에 대해 가정하지 않습니다. 단, 해당 문자는 로케일에서 유효한 문자여야 합니다. 접미사에는 개행 문자를 사용할 수 없지만 이는 searchFiles.txt
.
답변2
나는 주석에서 논의된 DOS 스타일 줄 끝을 수정했으며 searchFiles.txt
실제로 빈 줄을 포함하지 않는다고 가정합니다.
-name
테스트에서는 find
하나의 파일 이름 패턴만 사용합니다. 패턴에는 쉘 glob 문자가 포함될 수 있지만 쉘이 파일 이름을 조기에 생성하지 않도록 이러한 문자를 보호해야 합니다. 논리적 OR을 사용하여 이러한 테스트를 여러 개 결합할 수 있지만 -o
연산자 우선 순위에 주의해야 합니다.
쉘이 배열을 지원하는 경우 이를 수행할 수 있는 한 가지 방법은 다음과 같습니다( bash
여기서는 이를 사용하고 있지만 비슷한 프로세스가 다른 쉘에서도 작동해야 합니다).
files=( -false )
while IFS= read -r f || [ -n "$f" ]; do files+=( -o -name "*$f"); done < searchFiles.txt
${files[@]}
이로 인해 대체 서비스로 확장 되어야 합니다 .
-false -o -name *file.a.txt -o -name *file.b.txt -o -name *file.c.txt -o -name *file.d.txt -o -name *file.e.txt
find
그런 다음 다음 과 같은 명령 에서 사용할 수 있습니다 .
find . \( "${files[@]}" \) -exec grep -Ho '[^ ]*_XYZ' {} +
( 옵션을 /dev/null
추가하기 위해 더미 파일을 생략했습니다 ).-H
파일 수가 searchFiles.txt
너무 많으면 제한으로 인해 이 방법이 실패할 수 있습니다 ARG_MAX
.searchFiles.txt
여러 개의 작은 파일로 분할하여 이 제한 사항을 해결할 수 있습니다 .
답변3
grep -f
포함할 이름을 사용하여 텍스트 파일을 통해 파일 이름을 디렉터리로 필터링할 수 있습니다(부분 일치 허용). 그런 다음 이러한 파일은 수많은 grep
검색 패턴을 거쳐 최종적으로 작은 awk
.
GNU 사용 bash
:
grep -Ff filenames.txt <(printf '%s\n' *) |
xargs -d '\n' grep -oH '[^[:space:]]*_XYZ$' | awk -F: '
{f[$1] = f[$1] ? f[$1] ";" $2 : $0}
END {for (x in f) print f[x]}'
몇 가지 가정(문제는 아직 완전히 명확하지 않음):
- 파일 이름은 편리하게 개행이나 콜론(출력용
grep
)이 없습니다. 공백이 처리되었습니다. - 거기에는 일치하는 하위 디렉터리가 없습니다. 그렇지 않으면 두 번째 하위 디렉터리가
grep
메시지를 표시하지만 결과를 반환합니다. - 두 번째는
grep
줄 끝에서 패턴을 찾습니다. 단어 끝을 일치시키려는 경우 이를 수정할 수 있습니다. -H
하나의 파일이 있는grep
극단적인 경우 파일 이름을 출력에 인쇄합니다(파일이 두 개 이상인 경우 기본값입니다).