Ваша идея верна, но аргумент входного файла/строки находится не в том месте. Это должно было быть написано как
grep "Gary:" "$1" | grep -v "\(said\|told\)"
, что означает применение первого выражения grep
для сопоставления всех строк, содержащих Gary
, и строк фильтрации, содержащих слова said
или told
.
При вашей попытке, начиная с запуска с конвейером, запускаются оба процесса grep
, но часть после |
обрабатывает ввод $1
, как если бы это был файл, вместо того, чтобы получать ввод из стандартного ввода, а не из pipe и печатает пару строк, которые вы видите.
Но в это время вы видите, что терминал зависает, потому что первый grep
все еще ожидает ввода в своем стандартном потоке ввода, но не видит его. Нажатие Ctrl -C отправляет сигнал SIGINT, который в конечном итоге уничтожает конвейер.
Вместо чтения каждой строки, что не очень оптимально, используйте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
Это не работает с именами файлов, содержащими пробелы или новые строки. Предостережение для покупателя.
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"}'
Основываясь на коде в вашем посте, я предполагаю, что он может быть из этого поста.
Хотя это не лучший способ сделать это, вместо этого вы можете использовать следующий:
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}'
Строку 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
для чтения списка, передаваемого по каналу.
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 }'
Попробуйте это:
#!/bin/bash
export total=$(find. -name '*.txt' -exec wc -l "{}" ";" | awk 'BEGIN{sum=0} {sum+=$1} END{print sum}')
echo TOTAL LINES COUNTED ${total}