Como contar o número total de linhas de todos os arquivos .txt?

Como contar o número total de linhas de todos os arquivos .txt?

Estou tentando descobrir como obter o número total de linhas de todos os arquivos .txt. Acho que o problema está na linha 6 -> let $((total = total + count )). Alguém sabe o que há para corrigir a forma disso?

#!/bin/bash
total=0
find /home -type f -name "*.txt" | while read -r FILE; do
          count=$(grep -c ^ < "$FILE")
           echo "$FILE has $count lines"
           let $((total = total + count ))
        done
        echo TOTAL LINES COUNTED:  $total

Obrigado

Responder1

Sua linha 6 é melhor escrita como

total=$(( total + count ))

... mas seria melhor ainda usar uma ferramenta que fossefeitopara contar linhas (supondo que você queira contar novas linhas, ou seja, o número de linhas terminadas corretamente)

find . -name '*.txt' -type f -exec cat {} + | wc -l

Isso localiza todos os arquivos regulares no diretório atual ou abaixo dele que tenham nomes de arquivos terminando em .txt. Todos esses arquivos são concatenados em um único fluxo e canalizados para wc -l, que gera o número total de linhas, que é o que o título e o texto da pergunta pedem.

Roteiro completo:

#!/bin/sh

nlines=$( find . -name '*.txt' -type f -exec cat {} + | wc -l )

printf 'Total number of lines: %d\n' "$nlines"

Para obter também a contagem de linhas dos arquivos individuais, considere

find . -name '*.txt' -type f -exec sh -c '
    wc -l "$@" |
    if [ "$#" -gt 1 ]; then
        sed "\$d"
    else
        cat
    fi' sh {} + |
awk '{ tot += $1 } END { printf "Total: %d\n", tot }; 1'

Isso chama wc -llotes de arquivos, gerando a linha cound para cada arquivo individual. Quando wc -lé chamado com mais de um nome de arquivo, irá gerar uma linha no final com a contagem total. Excluímos esta linha sedse o script in-line sh -cfor chamado com mais de um argumento de nome de arquivo.

A longa lista de contagens de linhas e nomes de caminhos de arquivos é então passada para awk, que simplesmente soma as contagens (e passa os dados) e apresenta ao usuário a contagem total no final.


Em sistemas GNU, a wcferramenta pode ler nomes de caminhos de um fluxo delimitado por nulo. Você pode usar isso finde sua -print0ação nesses sistemas da seguinte forma:

find . -name '*.txt' -type f -print0 |
wc --files0-from=- -l

Aqui, os nomes de caminho encontrados são passados ​​como uma lista delimitada por nulos no canal para wcusar o arquivo -print0. O wcutilitário é usado com a opção não padrão --files0-frompara ler a lista que está sendo passada pelo canal.

Responder2

let $((total = total + count ))

Isso funciona, mas é um pouco redundante, já que ambos letiniciam $(( .. ))a expansão aritmética.

Qualquer um de let "total = total + count", let "total += count", : $((total = total + count))ou total=$((total + count))faria isso sem a duplicação. Os dois últimos deveriam ser compatíveis com um shell padrão, letnão é.

total=0
find /home -type f -name "*.txt" | while read -r FILE; do
    total=...
done
echo TOTAL LINES COUNTED:  $total

Você não disse a que problema se refere, mas um problema que você tem aqui é que no Bash, as partes de um pipeline são executadas em subshells por padrão, portanto, quaisquer alterações feitas totaldentro dowhile loop não são visíveis depois dele. Ver:Por que minha variável é local em um loop 'while read', mas não em outro loop aparentemente semelhante?

Você poderia usar shopt -s lastpipepara que a última parte do pipeline fosse executada no shell; ou agrupe o whilee echo:

find ... | { while ...
    done; echo "$total"; }

Claro, find ... | while read -r FILE;terá problemas com nomes de arquivos que contêm novas linhas ou iniciam/terminam com espaços em branco. Você poderia consertar isso com

find ... -print0 | while IFS= read -r -d '' FILE; do ...

ou, se você não se importa com o detalhamento da contagem de linhas por arquivo e sabe que seus arquivos são arquivos de texto completos, sem que nenhum perca a nova linha final, você pode simplesmente concatenar todos os arquivos e executá- wc -llos.

Se seus arquivos podem estar faltando a nova linha no final da última linha e você deseja contar a linha final incompleta, então você não pode fazer isso e precisa continuar usando grep -c ^em vez de wc -l. (Contar a linha parcial final é praticamente o único motivo para usar grep -c ^em vez de wc -l.)

Ver:Qual é o sentido de adicionar uma nova linha ao final de um arquivo?ePor que os arquivos de texto deveriam terminar com uma nova linha?em SO.

Além disso, se você deseja apenas a contagem total, todos os arquivos que correspondem ao padrão são arquivos regulares (para que o -type fteste possa ser eliminado) e você possui Bash e GNU grep, também pode fazer:

shopt -s globstar
shopt -s dotglob
grep -h -c ^ **/*.txt | awk '{ a += $0 } END { print a }'

**/*.txté um globo recursivo, precisa ser explicitamente habilitado para funcionar. dotglobfaz com que esse glob também corresponda a nomes de arquivos começando com um ponto. grep -hsuprime os nomes dos arquivos da saída e o awkscript conta a soma. Como nenhum nome de arquivo é impresso, isso deve funcionar mesmo que alguns deles sejam problemáticos.

Ou, como sugerido por @fra-san, com base em outra resposta agora excluída:

grep -r -c -h --include='*.sh' ^ |awk '{ a+= $0 } END {print a }'

Responder3

let total+=countfuncionará, não há necessidade $(( ))desta forma de avaliação aritmética.

Mas seria muito melhor fazer isso com wc -l.

find /home -type f -name '*.txt' -exec wc -l {} +

Se você deseja uma saída personalizada como em seu script de shell acima, OU se é provável que haja mais nomes de arquivos do que cabem no limite de comprimento de linha de ~ 2 MB do bash no Linux, você pode usar awkouperl para fazer a contagem. Qualquer coisa é melhor que um loop while-read do shell (vejaPor que usar um loop de shell para processar texto é considerado uma prática inadequada?). Por exemplo:

find /home -type f -name '*.txt' -exec perl -lne '
  $files{$ARGV}++;

  END {
    foreach (sort keys %files) {
      printf "%s has %s lines\n", $_, $files{$_};
      $total+=$files{$_}
    };
    printf "TOTAL LINES COUNTED: %s\n", $total
  }' {} +

Nota: o find ... -exec perlcomando acima irá ignorar arquivos vazios, enquanto a wc -lversão os listaria com uma contagem de linhas 0. É possível fazer com que o perl faça o mesmo (veja abaixo).

OTOH, ele fará uma contagem de linhas e totalizaráqualquernúmero de arquivos, mesmo que nem todos caibam em uma linha de comando do shell - a wc -lversão seria impressadoisou mais totallinhas nesse caso - provavelmente não vai acontecer, mas não é o que você gostaria se acontecesse.

Isso deve funcionar, usa wc -le canaliza a saída para perl para alterá-la para o formato de saída desejado:

$ find /home -type f -name '*.txt' -exec wc -l {} + |
    perl -lne 'next if m/^\s+\d+\s+total$/;
               s/\s+(\d+)\s+(.*)/$2 has $1 lines/;
               print;
               $total += $1;

               END { print "TOTAL LINES COUNTED:  $total"}'

Responder4

Experimente isto:

#!/bin/bash
export total=$(find . -name '*.txt' -exec wc -l "{}" ";" | awk 'BEGIN{sum=0} {sum+=$1} END{print sum}')
echo TOTAL LINES COUNTED ${total}

informação relacionada