如何计算所有 .txt 文件的总行数?

Ota*_*vor 3 bash text-processing

我想弄清楚如何从所有 .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
Run Code Online (Sandbox Code Playgroud)

谢谢

Kus*_*nda 15

你的第 6 行最好写成

total=$(( total + count ))
Run Code Online (Sandbox Code Playgroud)

...但它会更好,还是为使用的工具做出计数行(假设你要计算换行符,即正确终止的行数)

find . -name '*.txt' -type f -exec cat {} + | wc -l
Run Code Online (Sandbox Code Playgroud)

这将查找当前目录中或以下所有文件名以.txt. 所有这些文件都连接成一个流并通过管道传输到wc -l,它输出总行数,这就是问题的标题和文本所要求的。

完整脚本:

#!/bin/sh

nlines=$( find . -name '*.txt' -type f -exec cat {} + | wc -l )

printf 'Total number of lines: %d\n' "$nlines"
Run Code Online (Sandbox Code Playgroud)

要获得单个文件的行数,请考虑

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'
Run Code Online (Sandbox Code Playgroud)

这会调用wc -l成批的文件,输出每个单独文件的行数。当wc -l使用多个文件名调用时,它会在末尾输出一行并显示总数。sed如果sh -c使用多个文件名参数调用内嵌脚本,我们将删除这一行。

然后将行数和文件路径名的长列表传递给awk,它只是将计数相加(并传递数据)并在最后向用户显示总计数。


在 GNU 系统上,该wc工具可以从空分隔的流中读取路径名。您可以在这些系统上使用find它及其-print0操作,如下所示:

find . -name '*.txt' -type f -print0 |
wc --files0-from=- -l
Run Code Online (Sandbox Code Playgroud)

在这里,找到的路径名作为空分隔列表通过管道传递给wc使用非标准的-print0. 该wc实用程序与非标准--files0-from选项一起使用以读取通过管道传递的列表。

  • 为什么这么复杂?不会`shopt -s globstar; wc -l **/*.txt` 就够了吗? (2认同)
  • @user000001 当然是的,如果你可以_保证_`**/*.txt` 扩展到一个足够短的列表,不会触发“参数列表太长”错误。在一般情况下你不能这样做,并且问题中的用户没有指出所涉及的文件数量。 (2认同)

cas*_*cas 5

let total+=count会起作用,不需要$(( ))这种形式的算术评估。

但你最好用 来做到这一点wc -l

find /home -type f -name '*.txt' -exec wc -l {} +
Run Code Online (Sandbox Code Playgroud)

如果您想要像上面的 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
  }' {} +
Run Code Online (Sandbox Code Playgroud)

注意: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"}'
Run Code Online (Sandbox Code Playgroud)


ilk*_*chu 5

let $((total = total + count ))
Run Code Online (Sandbox Code Playgroud)

这工作,但它是一个有点多余,因为两者let$(( .. ))开始算术扩展。

let "total = total + count", let "total += count", : $((total = total + count))or 中的任何一个total=$((total + count))都可以在没有重复的情况下进行。最后两个应该与标准外壳兼容,let不是。

total=0
find /home -type f -name "*.txt" | while read -r FILE; do
    total=...
done
echo TOTAL LINES COUNTED:  $total
Run Code Online (Sandbox Code Playgroud)

你没有说你的意思是什么问题,但你在这里遇到的一个问题是,在 Bash 中,管道的一部分默认在子 shell 中运行,因此totalwhile循环内部所做的任何更改在它之后都不可见。请参阅:为什么我的变量在一个“while read”循环中是局部变量,而在另一个看似相似的循环中却不是?

您可以使用shopt -s lastpipe在 shell 中运行管道的最后一部分;或将whileand分组echo

find ... | { while ...
    done; echo "$total"; }
Run Code Online (Sandbox Code Playgroud)

当然,find ... | while read -r FILE;包含换行符或以空格开头/结尾的文件名会有问题。你可以用

find ... -print0 | while IFS= read -r -d '' FILE; do ...
Run Code Online (Sandbox Code Playgroud)

或者,如果您不关心每个文件行数的细分并且知道您的文件是完整的文本文件,并且没有丢失最后的换行符,您可以简单地将所有文件连接在一起并在其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 }'
Run Code Online (Sandbox Code Playgroud)

**/*.txt是一个递归全局变量,它需要显式启用才能工作。dotglob使该 glob 也匹配以点开头的文件名。grep -h抑制输出中的文件名,awk脚本计算总和。由于没有打印文件名,即使其中一些有问题,这也应该有效。

或者,正如@fra-san 所建议的,基于另一个现已删除的答案:

grep -r -c -h --include='*.sh' ^ |awk '{ a+= $0 } END {print a }'
Run Code Online (Sandbox Code Playgroud)


Nor*_*tfi -1

根据您帖子中的代码,我猜测它可能来自这篇文章。

虽然这不是最好的方法,但您可以使用以下方法:

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
Run Code Online (Sandbox Code Playgroud)

或与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
Run Code Online (Sandbox Code Playgroud)

您可能已经注意到shopt -s lastpipe, 这是因为循环在子 shell 中运行,因此不会在循环结束时while保留变量的新值...除非您在顶部使用此选项。total

或者如果你想要更快更短的东西:

find /path/to/directory/ -type f -name "*.txt" -exec wc -l {} \; | awk '{total += $1} END{print total}'
Run Code Online (Sandbox Code Playgroud)

  • 嗯,这里的前两个版本(使用 `while` 循环)不与问题中的问题相同:使用 Bash `$total` 在循环后打印为 `0` 吗? (3认同)