내 폴더에는 5000개의 파일이 있습니다. 파일 이름은 XX0000001~XX0005000입니다.
각 파일에서 단어를 가져온 다음 그 단어와 다른 파일(target.txt)의 다음 줄을 grep하려고 합니다.
내 XX* 파일 중 일부에는 약 30,000개의 단어가 포함되어 있습니다.
이를 수행할 수 있는 방법이 있습니까?
나는 시도했다:
start_number=0000001
end_number=0005000
words_file=target.txt
output_folder="output_results"
mkdir -p "$output_folder"
for ((i=start_number; i <=end_number; i++)); do
filename="XX$(printf "%07d" "$i")"
output_file="$output_folder/output_${filename}.txt"
while read -r word; do
awk -v word="$word" '{for (i=1; i<=NF; i++) if($1 ~ word) {print; next}}' "$filename" >> "$output_file"
done < "$words_file"
done
이 작업을 수행하는 더 빠른 방법이 있나요? 내 대상 파일에는 검색할 수백만 줄이 있습니다. 정확한 대상 파일 크기는 20GB, 106441678줄입니다.
예: XX0000001 파일은 다음과 같습니다.
Big1 Big5 Big7 Big10 Big11
(그리고 단어가 너무 많습니다. 일부 XX 파일에는 최대 30,000개의 단어가 포함될 수도 있습니다.)
Target.txt 파일은 다음과 같습니다.
#Big1
This_is_a_file_containing_xxxxx
#Big2
This_is_a_file_containing_xxxxx
#Big3
This_is_a_file_containing_xxxxx
#Big4
This_is_a_file_containing_xxxxx
#Big5
This_is_a_file_containing_xxxxx
#Big6
This_is_a_file_containing_xxxxx
#Big7
This_is_a_file_containing_xxxxx
#Big8
This_is_a_file_containing_xxxxx
#Big9
This_is_a_file_containing_xxxxx
#Big10
This_is_a_file_containing_xxxxx
#Big11
This_is_a_file_containing_xxxxx
#Big12
This_is_a_file_containing_xxxxx
답변1
내 폴더에는 5000개의 파일이 있습니다. 파일 이름은 XX0000001~XX0005000입니다.
파일 세트를 반복하려면 for f in XX0*
여기와 같은 쉘 glob을 사용하십시오. 일련의 숫자를 반복해야 하는 경우 Bash에서는 숫자를 8진수로 표시하므로 앞에 오는 0에 주의해야 합니다. 예를 들어 루프를 시도해 보고 숫자를 인쇄하고 마지막 숫자를 살펴보세요.
start_number=0000001
end_number=0005000
for ((i=start_number; i <=end_number; i++)); do
echo $i
done |tail -1
출력은 yes 2560
, no 입니다 5000
. 하지만 zsh에서는 그렇지 않으며 어떤 셸을 실행하고 있는지 언급하지 않았지만 문제는 지적할 가치가 있을 것입니다.
여기,
while read -r word; do
awk -v word="$word" '{for (i=1; i<=NF; i++) if($1 ~ word) {print; next}}' "$filename" >> "$output_file"
done < "$words_file"
이것이 무엇을 하는지는 잘 모르겠지만 AWK 스크립트에서는 입력 행의 모든 필드를 반복하지만 $1
루프 내에서는 필드 1()만 참조한다는 것을 알았습니다.
이제 파일이 다음과 같다고 가정합니다.
% cat XX0000001
Big1 Big7
% cat XX0000002
Big5 Big10
% cat target.txt
#Big1
This_is_a_file_containing_xxxxx
#Big2
This_is_a_file_containing_xxxxx
[...]
Big1
즉 , 파일의 한 줄에 여러 가지 다른 패턴(예: 및 )이 있습니다 (예: 한 줄에 하나씩). 또한 어떤 패턴과도 일치하는 줄을 찾아 다음 줄과 함께 인쇄하고 싶을 것 같습니다.Big7
XX0*
target.txt
이제 표준 grep은 일치 후 "한 줄 더"를 인쇄할 수 있으며 동시에 여러 패턴을 찾을 수 있습니다. 이 -f
옵션은 라인이 패턴을 형성하는 파일의 이름을 사용하므로 XX0*
각 패턴이 단일 라인으로 나타나도록 파일을 전처리해야 합니다. 모든 공백을 개행 문자로 변경하면 됩니다 tr
. 가장 쉬운 방법은 프로세스 대체를 사용하여 tr
to의 출력을 grep
파일로 사용하는 것이지만 임시 파일을 사용할 수도 있습니다(또는 출력을 to로 파이프할 수도 있음 tr
) grep -f -
.
예를 들어:
% grep -A1 -f <(tr ' ' '\n' < XX0000001 ) target.txt
#Big1
This_is_a_file_containing_xxxxx
--
#Big7
This_is_a_file_containing_xxxxx
--
#Big10
This_is_a_file_containing_xxxxx
물론 Big1
해당 라인에서도 패턴이 발생하므로 #Big10
일치합니다. (그러나 grep 옵션을 사용하여 전체 단어 일치를 요청할 수 있습니다 -w
.) 구분 기호를 제거하려면 --
결과를 파이프할 수 있습니다 grep -ve --
.
이것이 얼마나 효율적인지는 grep 구현에 따라 달라질 수 있지만 이러한 목적으로 설계된 도구로서 쉘 스크립트에서 동일한 작업을 수행하는 것보다 최적화 가능성이 더 높습니다. 쉘 스크립트는 느립니다. 모든 패턴이 형식이라면 Big*
공통 부분을 한 번만 찾는 것이 현명합니다. 패턴 목록을 단일 패턴으로 변경할 수도 있습니다. Big(1|5|7|10)
정규식 엔진에 더 잘 작동할 수 있기를 바랍니다.
답변2
많은 수의 검색어에 대해 매우 큰 파일을 검색하고 있으며 셸/표준 도구에서 "빠른" 솔루션을 찾을 가능성이 거의 없습니다. 즉, 귀하의 접근 방식이 특히 비효율적이라고 생각합니다.
아마도 다음과 같을 것입니다(테스트되지 않음):
words_file=target.txt
output_folder="output_results"
mkdir -p "$output_folder"
for filename in XX* ; do
output_file="$output_folder/output_${filename}.txt"
grep -f "$filename" -Fx -A1 "$words_file" > "$output_file"
done
하지만 솔직히 그 속도도 빠르지는 않을 것 같아요.
이는 다중 스레드일 수 있으며, 이는 처리량을 향상시킬 수 있습니다.
이는 다중 스레드 대안입니다. threads
이 값을 운영 환경에 맞게 조정 해야 합니다 .
threads=4
words_file=target.txt
output_folder="output_results"
mkdir -p "$output_folder"
find . -maxdepth 1 -type f -name 'XX*' -print0 | \
xargs -I% -0 -P$threads bash -c '
file="%"
output_file="$output_folder/output_${file#./}.txt"
grep -f "%" -Fx -A1 "$words_file" > "$output_file"
'
이를 통해 얻을 수 있는 이점은 사용 가능한 메모리, CPU 코어 수, 저장소 속도 및 서버의 기타 활동과 같은 하드웨어 요소에 따라 달라집니다.
답변3
귀하의 질문이 불분명하고 예상되는 출력을 제공하지 않아 필요한 것이 무엇인지 명확히 하는 데 도움이 되지 않으며 잠재적인 솔루션이 작동하는지 테스트할 방법이 없지만 awk를 사용하여 아마도 원하는 것일 수 있습니다. :
awk '
FILENAME != ARGV[ARGC-1] {
for ( i=1; i<=NF; i++ ) {
words[$i]
}
next
}
f {
print
}
{
f = 0
for ( word in words ) {
if ( $0 ~ word ) {
print
f = 1
next
}
}
}
' some_folder/XX* target.txt
XX*
파일이 너무 많으면 다음 ARG_MAX
과 같이 변경하세요.
printf '%s\n' some_folder/XX* |
awk '
FILENAME == "-" {
ARGV[ARGC++] = $0
next
}
FILENAME != ARGV[ARGC-1] {
for ( i=1; i<=NF; i++ ) {
words[$i]
}
next
}
f {
print
}
{
f = 0
for ( word in words ) {
if ( $0 ~ word ) {
print
f = 1
next
}
}
}
' - target.txt