bash 프로그래밍은 파일을 한 줄씩 비교하고 새 파일을 만듭니다.

bash 프로그래밍은 파일을 한 줄씩 비교하고 새 파일을 만듭니다.

두 개의 텍스트 파일이 있습니다. 파일 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일치하는 항목을 검색할 수 있지만 이 방법은 제가 보여드린 첫 번째 방법만큼 효율적이지 않습니다. 검색의 모든 줄을 검색합니다.file2file1file2file1

$ 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:-fPOSIX 표준):

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

관련 정보