如何计算所有.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工具可以从 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 行长度限制,您可以使用awkperl来进行计数。任何东西都比 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}

相关内容