두 개의 텍스트 파일이 있습니다. 파일 2에는 1,000,000개가 넘는 로그가 있습니다. 파일 1에는 IP 주소가 한 줄씩 포함되어 있습니다. 파일 2 줄을 읽고 파일 1에서 다음 줄을 검색하고 싶습니다. 즉, 다음과 같습니다.
파일 1:
34.123.21.32
45.231.43.21
21.34.67.98
파일 2:
34.123.21.32 0.326 - [30/Oct/2013:06:00:06 +0200]
45.231.43.21 6.334 - [30/Oct/2013:06:00:06 +0200]
45.231.43.21 3.673 - [30/Oct/2013:06:00:06 +0200]
34.123.21.32 4.754 - [30/Oct/2013:06:00:06 +0200]
21.34.67.98 1.765 - [30/Oct/2013:06:00:06 +0200]
...
파일 1의 IP를 파일 2에서 한 줄씩 검색하고 시간 매개변수(예: 0.326)를 새 파일에 인쇄하고 싶습니다.
어떻게 해야 하나요?
답변1
조인+정렬
두 가지 모두에 존재하는 IP를 찾으려는 경우 해당 명령을 사용할 수 있지만 파일을 결합하기 전에 해당 명령을 사용하여 파일을 미리 정렬 join
해야 합니다 .sort
$ join -o 2.2 <(sort file1) <(sort file2)
예
$ join -o 2.2 <(sort file1) <(sort file2)
1.765
0.326
4.754
3.673
6.334
다른 예시
파일 1a:
$ cat file1a
34.123.21.32
45.231.43.21
21.34.67.98
1.2.3.4
5.6.7.8
9.10.11.12
문서 2a:
$ cat file2a
34.123.21.32 0.326 - [30/Oct/2013:06:00:06 +0200]
45.231.43.21 6.334 - [30/Oct/2013:06:00:06 +0200]
45.231.43.21 3.673 - [30/Oct/2013:06:00:06 +0200]
34.123.21.32 4.754 - [30/Oct/2013:06:00:06 +0200]
21.34.67.98 1.765 - [30/Oct/2013:06:00:06 +0200]
1.2.3.4 1.234 - [30/Oct/2013:06:00:06 +0200]
4.3.2.1 4.321 - [30/Oct/2013:06:00:06 +0200]
실행 join
명령:
$ join -o 2.2 <(sort file1) <(sort file2)
1.234
1.765
0.326
4.754
3.673
6.334
노트:file2
먼저 정렬했기 때문에 이 방법을 사용하면 원래 순서가 손실됩니다. 그러나 결과적으로 이 방법에는 이제 file2
한 번의 스캔만 필요합니다.
grep
행을 사용하여 grep
일치하는 항목을 검색할 수 있지만 이 방법은 제가 보여드린 첫 번째 방법만큼 효율적이지 않습니다. 검색의 모든 줄을 검색합니다.file2
file1
file2
file1
$ grep -f file1 file2 | awk '{print $2}'
예
$ grep -f file1 file2 | awk '{print $2}'
0.326
6.334
3.673
4.754
1.765
1.234
grep 성능 향상
grep
다음 형식을 사용하면 성능 속도를 높일 수 있습니다.
$ LC_ALL=C grep -f file1 file2 | awk '{print $2}'
grep
또한 의 척추가 file1
고정된 길이( )인 것을 볼 수 있으며 -F
이는 더 나은 성능을 얻는 데도 도움이 됩니다.
$ LC_ALL=C grep -Ff file1 file2 | awk '{print $2}'
일반적으로 소프트웨어에서 말하면 이 접근 방식은 기본적으로 루프 유형 솔루션 내의 루프이므로 피하려고 합니다. 그러나 때로는 컴퓨터 + 소프트웨어를 사용하여 최상의 결과를 얻을 수 있습니다.
인용하다
답변2
스위치 (위치 grep
:-f
POSIX 표준):
sort file1 | uniq \ # Avoid duplicate entries in file1
| grep -f /dev/stdin file2 \ # Search in file2 for patterns piped on stdin
| awk '{print $2}' \ # Print the second field (time) for matches
> new_file # Redirect output to a new file
IP 주소가 에 여러 번 나타나면 file2
해당 시간 항목이 모두 인쇄됩니다.
이로써 내 시스템에서 2초 이내에 500만 줄의 파일 작업이 완료되었습니다.
답변3
질문의 이름을 지정했을 때배쉬 프로그래밍세미 배쉬 예제를 제출하겠습니다.
순수한 배쉬:
당신은 읽을 수있다IP 필터-file을 입력한 다음 한 줄씩 확인하고 일치시켜 보세요. 하지만 이 볼륨에서는 정말 느립니다.
버블링, 선택, 삽입, 병합 정렬 등을 쉽게 구현할 수 있습니다. 그러나 이러한 유형의 데이터 볼륨에서는 데이터 볼륨이 사라지고 아마도 행별 비교보다 더 나쁠 것입니다. (볼륨에 따라 많이 달라집니다.파일 필터링).
정렬+펀치:
또 다른 옵션은 이진 검색을 통해 파일을 정렬 sort
하고 내부적으로 입력을 처리하는 것입니다. 이 방법은 여기에 게시된 다른 제안보다 속도가 훨씬 느리지만 시도해 보겠습니다.
첫 번째는 bash 버전의 문제입니다. 버전 4(?)부터 mapfile
파일을 배열로 읽을 수 있습니다. 이것은 전통적인 것보다 훨씬 빠릅니다 read -ra …
. 이를 결합하면 sort
유사한 스크립트를 작성할 수 있습니다(이 작업에 대해).
mapfile arr <<< "$(sort -bk1,1 "$file_in")"
그런 다음 검색 알고리즘을 사용하여 해당 배열에서 일치하는 항목을 찾는 것에 대한 질문이 있습니다. 간단한 방법은 이진 검색을 사용하는 것입니다. 예를 들어 1,000,000개의 요소가 포함된 배열에서는 검색 속도가 매우 빠릅니다.
declare -i match_index
function in_array_bs()
{
local needle="$1"
local -i max=$arr_len
local -i min=0
local -i mid
while ((min < max)); do
(( (mid = ((min + max) >> 1)) < max )) || break
if [[ "${arr[mid]// *}" < "$needle" ]]; then
((min = mid + 1))
else
max=$mid
fi
done
if [[ "$min" == "$max" && "${arr[min]// *}" == "$needle" ]]; then
match_index=$min
return 0
fi
return 1
}
그러면 당신은 이렇게 말할 것입니다:
for x in "${filter[@]}"; do
if in_array_bs "$x"; then
… # check match_index+0,+1,+2 etc. to cover duplicates.
샘플 스크립트. (디버깅되지 않음) 단지 시작용일 뿐입니다. 사람들이 단지 의지하고 싶은 작은 볼륨의 경우 sort
템플릿이 될 수 있습니다. 하지만 또훨씬 느림:
#!/bin/bash
file_in="file_data"
file_srch="file_filter"
declare -a arr # The entire data file as array.
declare -i arr_len # The length of "arr".
declare -i index # Matching index, if any.
# Time print helper function for debug.
function prnt_ts() { date +"%H:%M:%S.%N"; }
# Binary search.
function in_array_bs()
{
local needle="$1"
local -i max=$arr_len
local -i min=0
local -i mid
while ((min < max)); do
(( (mid = ((min + max) >> 1)) < max )) || break
if [[ "${arr[mid]// *}" < "$needle" ]]; then
((min = mid + 1))
else
max=$mid
fi
done
if [[ "$min" == "$max" && "${arr[min]// *}" == "$needle" ]]; then
index=$min
return 0
fi
return 1
}
# Search.
# "index" is set to matching index in "arr" by `in_array_bs()`.
re='^[^ ]+ +([^ ]+)'
function search()
{
if in_array_bs "$1"; then
while [[ "${arr[index]// *}" == "$1" ]]; do
[[ "${arr[index]}" =~ $re ]]
printf "%s\n" "${BASH_REMATCH[1]}"
((++index))
done
fi
}
sep="--------------------------------------------"
# Timestamp start
ts1=$(date +%s.%N)
# Print debug information
printf "%s\n%s MAP: %s\n%s\n" \
"$sep" "$(prnt_ts)" "$file_in" "$sep" >&2
# Read sorted file to array.
mapfile arr <<< "$(sort -bk1,1 "$file_in")"
# Print debug information.
printf "%s\n%s MAP DONE\n%s\n" \
"$sep" "$(prnt_ts)" "$sep" >&2
# Define length of array.
arr_len=${#arr[@]}
# Print time start search
printf "%s\n%s SEARCH BY INPUT: %s\n%s\n" \
"$sep" "$(prnt_ts)" "$file_srch" "$sep" >&2
# Read filter file.
re_neg_srch='^[ '$'\t'$'\n'']*$'
debug=0
while IFS=$'\n'$'\t'-" " read -r ip time trash; do
if ! [[ "$ip" =~ $re_neg_srch ]]; then
((debug)) && printf "%s\n%s SEARCH: %s\n%s\n" \
"$sep" "$(prnt_ts)" "$ip" "$sep" >&2
# Do the search
search "$ip"
fi
done < "$file_srch"
# Print time end search
printf "%s\n%s SEARCH DONE\n%s\n" \
"$sep" "$(prnt_ts)" "$sep" >&2
# Print total time
ts2=$(date +%s.%N)
echo $ts1 $ts2 | awk '{printf "TIME: %f\n", $2 - $1}' >&2