compara arquivos linha por linha e crie uma nova programação bash

2

Eu tenho dois arquivos de texto. O arquivo 2 tem logs acima de 1.000.000. O arquivo 1 tem endereços IP linha por linha. Eu quero ler as linhas do arquivo 2 e pesquisar essas linhas no arquivo 1, quero dizer:

arquivo 1:

34.123.21.32
45.231.43.21
21.34.67.98

arquivo 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]
...

Eu quero procurar o IP do arquivo 1 linha por linha no arquivo 2 e imprimir os argumentos de tempo (exemplo: 0,326) para um novo arquivo.

Como posso fazer isso?

    
por DessCnk 04.11.2013 / 15:52

3 respostas

3

Juntar + ordenar

Se você está tentando encontrar IPs que estão presentes em ambos, você pode usar o comando join , mas precisará usar sort para pré-ordenar os arquivos antes de associá-los.

$ join -o 2.2 <(sort file1) <(sort file2)

Exemplo

$ join -o 2.2 <(sort file1) <(sort file2)
1.765
0.326
4.754
3.673
6.334

Outro exemplo

arquivo 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

arquivo 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]

Executando o comando join :

$ join -o 2.2 <(sort file1) <(sort file2)
1.234
1.765
0.326
4.754
3.673
6.334

NOTA: A ordem original de file2 é perdida com este método, devido ao fato de que a classificamos primeiro. No entanto, esse método só precisa varrer file2 uma vez, como resultado.

grep

Você pode usar grep para pesquisar correspondências em file2 usando linhas que estão em file1 , mas esse método não é tão eficiente quanto o primeiro método que mostrei. Está varrendo file2 procurando por cada linha em file1 .

$ grep -f file1 file2 | awk '{print $2}'

Exemplo

$ grep -f file1 file2 | awk '{print $2}'
0.326
6.334
3.673
4.754
1.765
1.234

Melhorando o desempenho do grep

Você pode acelerar o desempenho de grep usando este formulário:

$ LC_ALL=C grep -f file1 file2 | awk '{print $2}'

Você também pode informar a grep que as picadas em file1 têm comprimento fixo ( -F ), o que também ajudará a obter um melhor desempenho.

$ LC_ALL=C grep -Ff file1 file2 | awk '{print $2}'
Geralmente, no software, você tenta evitar essa abordagem, já que é basicamente um loop dentro de um tipo de solução de loop. Mas há momentos em que é o melhor que pode ser alcançado usando um computador + software.

Referências

por 05.11.2013 / 00:51
2

Você pode dizer a grep para obter seus padrões de um arquivo usando a opção -f (que está em o padrão 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

Observe que, se um endereço IP aparecer várias vezes em file2 , todas as entradas de tempo serão impressas.

Isso fez o trabalho em menos de 2 segundos em um arquivo de 5 milhões de linhas no meu sistema.

    
por 04.11.2013 / 16:48
1

Como você intitulou sua pergunta bash programming , eu enviarei um exemplo semi-bash.

Pure bash:

Você pode ler o arquivo ip filter e então verificar linha por linha e associá-lo a eles. Mas neste volume muito lento.

Você poderia implementar o bubble -, select -, insertion -, merge sort etc, mas, novamente, para esse tipo de volume, seria um caso perdido e, provavelmente, pior do que uma comparação por linha. (Depende muito do volume do arquivo de filtro ).

classificar + bash:

Outra opção seria classificar o arquivo com sort e processar a entrada internamente, por exemplo, pesquisa binária. Isso, também, seria muito mais lento do que as outras sugestões postadas aqui, mas vamos tentar.

Primeiramente, é uma questão sobre a versão bash. Pela versão 4 (?) Temos mapfile que lê arquivo para array. Isso é muito mais rápido do que o tradicional read -ra … . Combinado com sort , ele poderia ser roteirizado por algo como (para essa tarefa):

mapfile arr <<< "$(sort -bk1,1 "$file_in")"

Então, é uma questão de ter um algoritmo de busca para encontrar correspondências nessa matriz. Uma maneira simples poderia ser usar uma pesquisa binária. É eficiente e, e. Uma matriz de 1.000.000 elementos forneceria uma pesquisa bastante rápida.

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
}

Então você diria:

for x in "${filter[@]}"; do
    if in_array_bs "$x"; then
       … # check match_index+0,+1,+2 etc. to cover duplicates.

Um exemplo de script. (Não depurado), mas apenas como um iniciante. Para um volume menor, onde se desejaria depender apenas de sort , poderia ser um modelo. Mas novamente s.l.o.w.e.r. b.y a. l.o.t. :

#!/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
    
por 05.11.2013 / 04:51