¿Cómo contar el número total de líneas de todos los archivos .txt?

¿Cómo contar el número total de líneas de todos los archivos .txt?

Estoy tratando de descubrir cómo obtener el número total de líneas de todos los archivos .txt. Creo que el problema está en la línea 6 -> let $((total = total + count )). ¿Alguien sabe cuál es la forma correcta de esto?

#!/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

Gracias

Respuesta1

Su línea 6 se escribe mejor como

total=$(( total + count ))

... pero sería mejor aún utilizar una herramienta que seahechopara contar líneas (suponiendo que desee contar nuevas líneas, es decir, el número de líneas terminadas correctamente)

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

Esto busca todos los archivos normales en o debajo del directorio actual que tienen nombres de archivo que terminan en .txt. Todos estos archivos se concatenan en una única secuencia y se canalizan a wc -l, lo que genera el número total de líneas, que es lo que solicita el título y el texto de la pregunta.

Guión completo:

#!/bin/sh

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

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

Para obtener también el recuento de líneas de los archivos individuales, 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'

Esto requiere wc -llotes de archivos y genera la línea compuesta para cada archivo individual. Cuando wc -lse llama con más de un nombre de archivo, generará una línea al final con el recuento total. Eliminamos esta línea si se llama sedal script en línea con más de un argumento de nombre de archivo.sh -c

Luego, la larga lista de recuentos de líneas y nombres de rutas de archivos se pasa a awk, que simplemente suma los recuentos (y pasa los datos) y presenta al usuario el recuento total al final.


En los sistemas GNU, la wcherramienta puede leer nombres de rutas de una secuencia delimitada por nulos. Puedes usarlo con findy su -print0acción en estos sistemas de esta manera:

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

Aquí, los nombres de ruta encontrados se pasan como una lista delimitada por valores nulos a través de la canalización para wcusar el archivo no estándar -print0. La wcutilidad se utiliza con la opción no estándar --files0-frompara leer la lista que se pasa a través de la tubería.

Respuesta2

let $((total = total + count ))

Esto funciona, pero es un poco redundante, ya que ambos letinician $(( .. ))la expansión aritmética.

Cualquiera de let "total = total + count", let "total += count"o lo haría sin la duplicación : $((total = total + count)). total=$((total + count))Los dos últimos deberían ser compatibles con un shell estándar, letpero no lo es.

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

No dijiste a qué problema te refieres, pero un problema que tienes aquí es que en Bash, las partes de una canalización se ejecutan en subcapas de forma predeterminada, por lo que cualquier cambio realizado totaldentro del whilebucle no es visible después. Ver:¿Por qué mi variable es local en un bucle 'mientras se lee', pero no en otro bucle aparentemente similar?

Podría utilizar shopt -s lastpipeque la última parte de la canalización se ejecute en el shell; o agrupar el whiley echo:

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

Por supuesto, find ... | while read -r FILE;tendrá problemas con los nombres de archivos que contengan nuevas líneas o que comiencen/finalicen con espacios en blanco. Podrías arreglar eso con

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

o, si no le importa el desglose del recuento de líneas por archivo y sabe que sus archivos son archivos de texto completos, sin que a ninguno le falte la nueva línea final, simplemente puede concatenar todos los archivos y ejecutarlos wc -l.

Si a sus archivos les puede faltar la nueva línea al final de la última línea y desea contar esa última línea incompleta, entonces no puede hacerlo y debe seguir usando grep -c ^en lugar de wc -l. (Contar la línea parcial final es prácticamente la única razón para usar grep -c ^en lugar de wc -l).

Ver:¿Cuál es el punto de agregar una nueva línea al final de un archivo?y¿Por qué los archivos de texto deberían terminar con una nueva línea?en lo.

Además, si solo desea el recuento total, todos los archivos que coinciden con el patrón son archivos normales (por lo que -type fse puede descartar la prueba), y tiene Bash y GNU grep, también puede hacer:

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

**/*.txtes un globo recursivo, debe habilitarse explícitamente para funcionar. dotglobhace que ese globo también coincida con nombres de archivos que comienzan con un punto. grep -hsuprime los nombres de archivos de la salida y el awkscript cuenta la suma. Dado que no se imprimen nombres de archivos, esto debería funcionar incluso si algunos de ellos son problemáticos.

O, como lo sugiere @fra-san, basado en otra respuesta ahora eliminada:

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

Respuesta3

let total+=countfuncionará, no hay necesidad de hacerlo $(( ))con esta forma de evaluación aritmética.

Pero sería mucho mejor hacer esto con wc -l.

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

Si desea una salida personalizada como en su script de shell anterior, O si es probable que haya más nombres de archivos de los que caben en el límite de longitud de línea de ~2 MB de bash en Linux, puede usar awko perlpara hacer el conteo. Cualquier cosa es mejor que un bucle mientras se lee el shell (consulte¿Por qué se considera una mala práctica utilizar un bucle de shell para procesar texto?). Por ejemplo:

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: el find ... -exec perlcomando anterior ignorará los archivos vacíos, mientras que la wc -lversión los enumerará con un recuento de líneas de 0. Es posible hacer que Perl haga lo mismo (ver más abajo).

OTOH, hará un recuento de líneas y un total decualquiercantidad de archivos, incluso si no caben todos en una línea de comando de shell: la wc -lversión se imprimirádoso más totallíneas en ese caso; probablemente no sucederá, pero no es lo que desearía si sucediera.

Esto debería funcionar, utiliza wc -ly canaliza la salida a Perl para cambiarla al formato de salida deseado:

$ 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"}'

Respuesta4

Prueba esto:

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

información relacionada