Usando o awk e fazendo o loop pelos arquivos em um diretório

2

Eu tenho um reviews_folder que contém arquivos diferentes, como hotel_72572.dat

Cada arquivo contém várias revisões estruturadas assim:

...
<Overall>4
...

Meu objetivo é calcular a média geral de todas as revisões de cada arquivo (hotel) usando um script averagereviews.sh. Executando: ./averagereviews.sh path_to_reviews_folder Eu deveria obter esta saída:

hotel_11212.dat 3.51
hotel_2121.dat 2.62
hotel_31212.dat 2.43
...

Meu script é:

#!/bin/bash
cd "$1" || exit 1
for file in "$1"; do
awk 'count+=sub(/<Overall>/, ""){sum+=$0}END{print sum/count}' file;
done

O problema é que ele não reconhece o arquivo como um diretório e se eu colocar o hotel _ *. dat ele fará a média de todos os arquivos existentes em reviews_folder e não para cada um deles.

    
por sj34 24.02.2018 / 19:25

4 respostas

4

Com um único script awk (sem for loop e várias awk invocations):

Exemplo de arquivos de entrada:

$ head reviews_folder/hotel_*.dat
==> reviews_folder/hotel_111.dat <==
<Overall>1
<Overall>4
<Overall>3

==> reviews_folder/hotel_222.dat <==
<Overall>11
<Overall>5
<Overall>7

==> reviews_folder/hotel_333.dat <==
<Overall>7
<Overall>4
<Overall>10
awk -F'>' 'fn && FILENAME != fn{ 
              sub(".*/", "", fn);
              print fn, sprintf("%.2f", sum/n); sum = 0
          }
          { sum += $2; n = FNR; fn = FILENAME }
          END{ 
              sub(".*/", "", fn);
              print fn, sprintf("%.2f", sum/n)
          }' reviews_folder/hotel_*.dat

A saída:

hotel_111.dat 2.67
hotel_222.dat 7.67
hotel_333.dat 7.00
    
por 24.02.2018 / 19:55
3

Com alguns aprimoramentos no seu script,

#!/bin/bash
cd "$1" || { printf 'unable to navigate to target\n' >&2; exit 1 ; }
for file in *.dat; do
    test -f "$file" || continue
    awk 'count+=sub(/<Overall>/, ""){sum+=$0}END{print (count)?(sum/count):0}}' "$file"
done
  1. Como você já é cd -o para "$1" você não precisa de for file in "$1" , mas apenas passa o mouse sobre as extensões de arquivo necessárias for file in *.dat
  2. A condição test -f "$file" || continue irá garantir que, se não houver arquivos no caminho que está sendo examinado, uma saída normal do loop for ocorre em vez de passar um glob não expandido para awk para processar
  3. Passe o nome do arquivo como $file em vez de uma string literal file . As variáveis do shell precisam ser prefixadas com um sinal $ antes do nome e geralmente precisam ser citadas duas vezes.
  4. Um pequeno aprimoramento na cláusula END de awk para verificar se a contagem é diferente de zero antes de dividir com ela.
por 24.02.2018 / 19:37
1

for file in "$1" executará o loop exatamente uma vez, com file definido para o valor literal do primeiro argumento para o script. Como "$1" é citado, os curingas não são expandidos. Se você passar um diretório para o script, também passará o nome do diretório para awk , e provavelmente não vai gostar muito disso, meu gawk diz:

gawk: warning: command line argument '/tmp/test/' is a directory: skipped

Se você deseja executar o loop sobre cada arquivo individualmente, use um caractere curinga no local correto. O * aqui será expandido para os nomes de arquivos no diretório atual, aquele dado como argumento, já que fizemos um cd lá:

#!/bin/sh
cd "$1" || exit 1 
for file in * ; do
    awk '...' "$file"
done

Como alternativa, você pode passar uma lista de nomes de arquivos como argumento para o script e, em seguida, fazer um loop sobre eles:

#!/bin/sh
for file in "$@" ; do
    awk '...' "$file"
done

Na prática, você faria myscript /some/path/hotel*.dat e deixaria o shell expandir os nomes dos arquivos para a linha de comando dos scripts. "$@" se expande para a lista de argumentos da linha de comando.

Dito isto, o script awk está um pouco fora também. Conforme você escreveu, a condição para a primeira regra é count+=sub(/<Overall>/, "") . Isso é verdade sempre que count for diferente de zero após a adição, independentemente de qual sub() retornou dessa vez. Isso significa que a regra {sum+=$0} é executada toda vez que <Overall> é visto pelo menos uma vez. Será somado sem aumentar count .

Você provavelmente vai querer algo assim:

awk '/^<Overall>/ {sub(/<Overall>/, ""); count += 1; sum += $0} END {print sum/count}' "$file"

Para exibir o nome do arquivo, você pode echo it:

#!/bin/sh
cd "$1" || exit 1 
for file in * ; do
    printf "%s " "$file"
    awk '/^<Overall>/ {sub(/<Overall>/, ""); count += 1; sum += $0} END {print sum/count}' "$file"
done
    
por 24.02.2018 / 19:52
0

Use o comando abaixo em cada arquivo. Você obterá a média. Testado e trabalhado bem

Entrada

<Overall>1
<Overall>4
<Overall>3

i='awk '{print NR}' hotel_111.dat| tail -1 '

awk -F ">" -v i="$i" 'BEGIN{sum=0} {sum=sum+$2} END{print FILENAME;print  sum/i}' hotel_111.dat  | sed "N;s/\n/ /g"

saída

hotel_111.dat 2.66667
    
por 25.02.2018 / 15:13

Tags