Как подсчитать общее количество строк во всех TXT-файлах?

Как подсчитать общее количество строк во всех TXT-файлах?

Я пытаюсь выяснить, как получить общее количество строк из всех .txt файлов. Я думаю, проблема в строке 6 -> let $((total = total + count )). Кто-нибудь знает, как это исправить?

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

Спасибо

решение1

Вашу строку 6 лучше записать так:

total=$(( total + count ))

... но все же лучше использовать инструмент, которыйсделалдля подсчета строк (предполагается, что вы хотите подсчитать новые строки, т.е. количество правильно завершенных строк)

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

Это находит все обычные файлы в текущем каталоге или ниже, имена которых заканчиваются на .txt. Все эти файлы объединяются в один поток и передаются в wc -l, который выводит общее количество строк, что и требуется в заголовке и тексте вопроса.

Полный сценарий:

#!/bin/sh

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

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

Чтобы также получить количество строк в отдельных файлах, рассмотрите

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'

Это вызывает wc -lпакеты файлов, выводя количество строк для каждого отдельного файла. При wc -lвызове с более чем одним именем файла, он выведет строку в конце с общим количеством. Мы удаляем эту строку, sedесли встроенный sh -cскрипт вызывается с более чем одним аргументом имени файла.

Затем длинный список количества строк и путей к файлам передается в awk, который просто суммирует количество (и передает данные) и в конце предоставляет пользователю общее количество.


В системах GNU wcинструмент может считывать имена путей из потока с разделителями-нулями. Вы можете использовать это с findи его -print0действием в этих системах следующим образом:

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

Здесь найденные пути передаются в виде списка с разделителями-нулями по каналу с wcиспользованием нестандартного -print0. wcУтилита используется с нестандартной --files0-fromопцией для чтения списка, передаваемого по каналу.

решение2

let $((total = total + count ))

Это работает, но немного избыточно, поскольку и letи $(( .. ))начинают арифметическое расширение.

Любой из let "total = total + count", let "total += count", : $((total = total + count))или total=$((total + count))сделает это без дублирования. Последние два должны быть совместимы со стандартной оболочкой, letне так ли?

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

Вы не сказали, какую проблему имеете в виду, но одна из проблем заключается в том, что в Bash части конвейера по умолчанию выполняются в подоболочках, поэтому любые изменения, внесенные totalвнутри whileцикла, не видны после него. Смотрите:Почему моя переменная является локальной в одном цикле «while read», но не в другом, на первый взгляд похожем цикле?

Вы можете использовать shopt -s lastpipeпоследнюю часть конвейера, запущенную в оболочке; или сгруппировать whileи echo:

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

Конечно, find ... | while read -r FILE;будут проблемы с именами файлов, которые содержат новые строки или начинаются/заканчиваются пробелами. Вы можете исправить это с помощью

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

или, если вас не волнует разбивка по количеству строк в каждом файле и вы знаете, что ваши файлы представляют собой полноценные текстовые файлы, в которых нет ни одного пропущенного символа новой строки, вы можете просто объединить все файлы вместе и запустить wc -lих.

Если в ваших файлах может отсутствовать символ новой строки в конце последней строки, и вы хотите подсчитать эту последнюю неполную строку, то вы не можете этого сделать и должны продолжать использовать grep -c ^вместо wc -l. (Подсчет последней неполной строки — это, по сути, единственная причина использовать grep -c ^вместо wc -l.)

Видеть:Какой смысл добавлять новую строку в конец файла?иПочему текстовые файлы должны заканчиваться новой строкой?на SO.

Кроме того, если вам нужно только общее количество, все файлы, соответствующие шаблону, являются обычными файлами (поэтому проверку -type fможно пропустить), и у вас есть Bash и GNU grep, вы также можете сделать следующее:

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

**/*.txtявляется рекурсивным глобом, его необходимо явно включить для работы. dotglobзаставляет этот глоб также соответствовать именам файлов, начинающимся с точки. grep -hподавляет имена файлов из вывода, и awkскрипт подсчитывает сумму. Поскольку имена файлов не выводятся, это должно работать, даже если некоторые из них проблемные.

Или, как предложил @fra-san, основываясь на другом, теперь уже удаленном, ответе:

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

решение3

let total+=countбудет работать, в этом нет необходимости $(( ))при такой форме арифметической оценки.

Но было бы гораздо лучше сделать это с помощью wc -l.

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

Если вам нужен пользовательский вывод, как в вашем скрипте оболочки выше, ИЛИ если, вероятно, будет больше имен файлов, чем поместится в ограничение длины строки bash в ~2 МБ на Linux, вы можете использовать awkили perlдля подсчета. Все лучше, чем цикл while-read оболочки (см.Почему использование цикла оболочки для обработки текста считается плохой практикой?). Например:

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
  }' {} +

Примечание: find ... -exec perlкоманда выше будет игнорировать пустые файлы, тогда как wc -lверсия будет перечислять их с количеством строк 0. Можно заставить Perl делать то же самое (см. ниже).

С другой стороны, он выполнит подсчет строк и общую сумму длялюбойколичество файлов, даже если они не поместятся в одну командную строку оболочки - версия wc -lвыведетдваили больше totalстрок в этом случае - возможно, этого не произойдет, но это не то, чего бы вы хотели, даже если бы это произошло.

Это должно работать, это использует wc -lи перенаправляет вывод в Perl, чтобы преобразовать его в желаемый формат вывода:

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

решение4

Попробуй это:

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

Связанный контент