como executar o awk duas vezes no mesmo arquivo

6

Eu tenho um arquivo de log com user_id e outra coluna com resultados do concurso. Eu gostaria de:

  1. encontre todos os user_ids dos usuários que ganharam
  2. dados os user_id's, retorne TODAS as entradas de log para esses usuários

Exemplo:

log.csv

id,user_id,winner,page_id
1,user_1,win,1
2,user_1,,10
3,user_2,,1
4,user_2,,2
5,user_4,win,10
6,user_5,,2
7,user_5,win,3

Dado um arquivo de log como este, atualmente estou fazendo isso como um processo de duas etapas:

Etapa 1: retorne cada linha mencionando a palavra 'win'

/win/ {
    FS=","

    # To make unique, store user_id's in array
    n[$2] = 0
}

# Print the unique array keys
END{
    for (x in n)
        print x
}

Isso produz:

user_1
user_4
user_5

Salvei esta saída no arquivo output.txt

Depois, passo esse arquivo e o arquivo de log original para outro arquivo awk:

NR == FNR{
    n[$1] = 0   # Set the user ID to the array
    next        # Go to the next file
}
{
    FS=","
    if($2 in n){
        print $0
    }
}

Isso retorna a saída correta (todas as linhas para cada um dos user_ids que ganhou):

1,user_1,win,1
2,user_1,,10
5,user_4,win,10
6,user_5,,2
7,user_5,win,3

Existe uma maneira mais elegante de fazer isso? Com um único arquivo awk?

    
por zbinsd 07.02.2015 / 01:45

6 respostas

3

Eu usaria dois arrays:

awk -F, '{a[$0]=$2;if($3=="win")b[$2]++}END{for(i in a){if(b[a[i]])print i}}'
    
por 07.02.2015 / 02:21
2

Is there a more elegant way to do this?

Sim, claro que existe. Basta executar o Awk duas vezes no mesmo arquivo (como você disse no seu título).

awk -F, '$3=="win"{won[$2]} FNR==NR{next} $2 in won' log.csv log.csv
    
por 11.10.2017 / 11:54
1

Como eu preenho grep é mais rápido que awk Então se você tem GNU grep com extensão perl você pode tentar

fgrep -f <(grep -oP "[^,]*(?=,win)" log.csv) log.csv

Sem perl.ext , você terá que direcionar grep de saída para cut

fgrep -f <(grep win log.csv | cut -d, -f2) log.csv

Ou use sed (parece ser mais rápido do que acima de grep | cut )

fgrep -f <(sed -n '/win/s/^[^,]*,\([^,]*\).*//p' log.csv) log.csv
    
por 07.02.2015 / 09:09
0

Mais tarde, mas para a posteridade, gostaria de salientar que você pode fazer isso:

awk '
   BEGIN 
   {
       while(getline < FILENAME)
       {    
           # do first pass stuff
       }
   }

   {
        # do second pass stuff
   }
' file

Se você quiser fazer mais passes, você pode close(FILENAME) após o primeiro loop while, então faça um segundo.

    
por 30.08.2017 / 15:59
0

Usando csvkit , pode-se fazer

$ csvsql --query 'SELECT b.* FROM log AS a JOIN log AS b USING (user_id) WHERE a.winner="win"' log.csv
id,user_id,winner,page_id
1,user_1,win,1
2,user_1,,10
5,user_4,win,10
6,user_5,,2
7,user_5,win,3

Mas não tenho tanta certeza de que será muito rápido em um arquivo de entrada enorme. Por padrão, um banco de dados SQLite é criado, preenchido e consultado em segundo plano.

Você pode fazer isso no shell diretamente também:

$ join -t ',' -1 2 -2 2 log.csv log.csv | awk -v FS=',' -v OFS=',' '$3 == "win" { print $5,$1,$6,$7 }'
1,user_1,win,1
2,user_1,,10
5,user_4,win,10
6,user_5,,2
7,user_5,win,3

Mais uma vez, não tenho certeza de como isso se aplica a arquivos enormes. Além disso, o arquivo de entrada deve ser classificado na coluna 2 (que são os dados do exemplo).

    
por 30.08.2017 / 16:24
0

Esta é uma solução completa de arquivo único do gnu awk você pode apenas executá-lo como: > awk -f singlestep.awk log.csv

BEGIN {
    FS=",";
    #you cannot use FILENAME , since in BEGIN section you are not processing any files and the FILENAME variable is empty
    # So you need to use the ARGV
    while(getline < ARGV[1])
    {
        if ($0 ~ /win/) {
            # To make unique, store user_id's in array
            n[$2] = 0;
        }
    }
}
{
    if ($2 in n)
    {
        print $0;
    }
}
    
por 11.10.2017 / 11:43

Tags