我试图弄清楚如何从所有 .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
工具可以从 nul 分隔流中读取路径名。您可以在这些系统上使用它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 中,管道的各个部分默认在子 shell 中运行,因此对循环total
内部所做的任何更改while
在循环之后都不可见。看:为什么我的变量在一个“while read”循环中是本地变量,但在另一个看似相似的循环中却不是?
您可以使用shopt -s lastpipe
让管道的最后一部分在 shell 中运行;或将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
。)
看:在文件末尾添加新行有什么意义?和为什么文本文件应该以换行符结尾?就这样。
另外,如果您只想要总数,所有与模式匹配的文件都是常规文件(因此-type f
可以删除测试),并且您有 Bash 和 GNU grep,您也可以这样做:
shopt -s globstar
shopt -s dotglob
grep -h -c ^ **/*.txt | awk '{ a += $0 } END { print a }'
**/*.txt
是一个递归 glob,需要显式启用它才能工作。dotglob
使该 glob 也匹配以点开头的文件名。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 {} +
如果您想要像上面的 shell 脚本一样自定义输出,或者如果文件名的数量可能超出 linux 上 bash 的 ~2MB 行长度限制,您可以使用awk
或perl
来进行计数。任何东西都比 shell while-read 循环更好(参见为什么使用 shell 循环处理文本被认为是不好的做法?)。例如:
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 执行相同的操作(见下文)。
OTOH,它将进行行计数和总计任何文件数量,即使它们不能全部容纳在一个 shell 命令行中 - 版本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}