
Я пытаюсь выяснить, как получить общее количество строк из всех .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}