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

Ваша идея верна, но аргумент входного файла/строки находится не в том месте. Это должно было быть написано как

grep "Gary:" "$1" | grep -v "\(said\|told\)"

, что означает применение первого выражения grepдля сопоставления всех строк, содержащих Gary, и строк фильтрации, содержащих слова saidили told.

При вашей попытке, начиная с запуска с конвейером, запускаются оба процесса grep, но часть после |обрабатывает ввод $1, как если бы это был файл, вместо того, чтобы получать ввод из стандартного ввода, а не из pipe и печатает пару строк, которые вы видите.

Но в это время вы видите, что терминал зависает, потому что первый grepвсе еще ожидает ввода в своем стандартном потоке ввода, но не видит его. Нажатие Ctrl -C отправляет сигнал SIGINT, который в конечном итоге уничтожает конвейер.

3
23.05.2021, 20:16
6 ответов

Вместо чтения каждой строки, что не очень оптимально, используйтеwc. Также поможет правильный синтаксис арифметических вычислений :total=$((total+count)).

#!/bin/bash

total=0
path=/home

for f in $(find $path -type f -name "*.txt"); do
    count=$(wc -l < $f)
    echo "$FILE has $count lines"
    total=$((total + count))
done
echo TOTAL LINES COUNTED:  $total

Это не работает с именами файлов, содержащими пробелы или новые строки. Предостережение для покупателя.

1
28.07.2021, 11:29

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

Но лучше сделать это с wc -l.

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

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

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перечислит их с нулевым количеством строк. Perl можно заставить делать то же самое (см. ниже ).

OTOH, он подсчитывает количество строк и суммирует любое количество файлов, даже если они не помещаются в одну командную строку оболочки -версия 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"}'
3
28.07.2021, 11:29

Основываясь на коде в вашем посте, я предполагаю, что он может быть из этого поста.

Хотя это не лучший способ сделать это, вместо этого вы можете использовать следующий:

shopt -s lastpipe
total=0
find pathhere -type f -name "*.txt" | while read FILE; do
     count=$(grep -c ^ < "$FILE")
     echo "$FILE has $count lines
     total=$((total + count))
done
echo TOTAL LINES COUNTED:  $total

или сwc:

shopt -s lastpipe
total=0
find pathhere -type f -name "*.txt" | while read FILE; do
     count=$(wc -l < "$FILE")
     echo "$FILE has $count lines"
     total=$((total + count))
done
echo TOTAL LINES COUNTED:  $total

Вы могли заметить shopt -s lastpipe, и это , потому что цикл whileвыполняется в подоболочке,и, таким образом, не переносит новое значение переменной totalв конце цикла... если вы не используете эту опцию вверху.

Или если вы хотите что-то быстрее и короче:

find /path/to/directory/ -type f -name "*.txt" -exec wc -l {} \; | awk '{total += $1} END{print total}'
-1
28.07.2021, 11:29

Строку 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для чтения списка, передаваемого по каналу.

15
28.07.2021, 11:29
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 }'
5
28.07.2021, 11:29

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

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

Теги

Похожие вопросы