
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 -l
lotes 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 sed
se o script in-line sh -c
for 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 wc
ferramenta pode ler nomes de caminhos de um fluxo delimitado por nulo. Você pode usar isso find
e sua -print0
açã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 wc
usar o arquivo -print0
. O wc
utilitário é usado com a opção não padrão --files0-from
para ler a lista que está sendo passada pelo canal.
Responder2
let $((total = total + count ))
Isso funciona, mas é um pouco redundante, já que ambos let
iniciam $(( .. ))
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, let
nã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 total
dentro 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 lastpipe
para que a última parte do pipeline fosse executada no shell; ou agrupe o while
e 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 -l
los.
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 f
teste 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. dotglob
faz com que esse glob também corresponda a nomes de arquivos começando com um ponto. grep -h
suprime os nomes dos arquivos da saída e o awk
script 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+=count
funcionará, 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 awk
ouperl
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 perl
comando acima irá ignorar arquivos vazios, enquanto a wc -l
versã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 -l
versão seria impressadoisou mais total
linhas nesse caso - provavelmente não vai acontecer, mas não é o que você gostaria se acontecesse.
Isso deve funcionar, usa wc -l
e 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}