抑制 `wc -l` 输出中的摘要信息

Suppressing summary information in `wc -l` output

我使用命令 wc -l 计算文本文件中的行数(我还想通过管道对所有内容进行排序),如下所示:

wc -l $directory-path/*.txt | sort -rn

输出包括"total"行,这是所有文件行的总和:

10 total
5 ./directory/1.txt
3 ./directory/2.txt
2 ./directory/3.txt

有什么办法可以抑制这个总结行吗?或者更好的是,改变摘要行的措辞方式?例如,单词 "lines" 代替“10”,单词 "file".

代替 "total"

你能用另一个厕所吗?

POSIX wc(man -s1p wc) 显示
如果指定了多个输入文件操作数,则应写入额外的一行,其格式与其他行相同,但应写入单词 total(在 POSIX 语言环境中)路径名和每列的总数应酌情书写。这样的附加行(如果有的话)写在输出的末尾。

你说 Total 行是第一行,手册说它是最后一行,而其他 wc 根本不显示它。删除第一行或最后一行是危险的,所以我会 grep -v 带有总计的行(在 POSIX 语言环境中......),或者只是 grep 所有其他行的斜杠:

wc -l $directory-path/*.txt | grep "/"

这实际上相当棘手。

我基于 wc 命令的 GNU coreutils 版本。请注意,total 行通常是最后打印的,而不是最先打印的(请参阅我对该问题的评论)。

wc -l 为每个输入文件打印一行,由文件中的行数和文件名组成。 (如果没有文件名参数,文件名将被省略;在这种情况下,它计算标准输入中的行数。)

当且仅当存在多个文件名参数时,它才会打印包含总行数和单词 total 的最后一行。文档表明无法禁止该摘要行。

除了它前面有其他输出之外,该行与名称恰好为 total.

的文件的输出没有区别

所以要可靠地过滤掉 total 行,您必须读取 wc -l 的所有输出,并且仅当输出的总长度大于时才删除最后一行1.(如果文件名中包含换行符,即使这样也会失败,但您可以忽略这种可能性。)

更可靠的方法是对每个文件分别调用 wc -l,避免 total 行:

for file in $directory-path/*.txt ; do wc -l "$file" ; done

如果你想对输出进行排序(你在评论中提到但在你的问题中没有提到):

for file in $directory-path/*.txt ; do wc -l "$file" ; done | sort -rn

如果您碰巧知道没有名为 total 的文件,一个快速而简单的方法是:

wc -l $directory-path/*.txt | grep -v ' total$'

如果您想 运行 wc -l 所有文件,然后过滤掉 total 行,这里有一个 bash 脚本可以完成这项工作。根据需要调整 *.txt

#!/bin/bash

wc -l *.txt > .wc.out
lines=$(wc -l < .wc.out)
if [[ lines -eq 1 ]] ; then
    cat .wc.out
else
    (( lines-- ))
    head -n $lines .wc.out
fi
rm .wc.out

另一种选择是这个 Perl 单行代码:

wc -l *.txt | perl -e '@lines = <>; pop @lines if scalar @lines > 1; print @lines'

@lines = <> 将所有输入都输入到一个字符串数组中。 pop @lines 如果有多于一个,则丢弃最后一行,即如果最后一行是 total 行。

程序wc,当有两个或两个以上文件时总是显示总数(wc.c的片段):

if (argc > 2)
     report ("total", total_ccount, total_wcount, total_lcount);
   return 0;

同样最简单的方法是只对一个文件使用 wc 并找到当前 - 一个接一个 - 文件到 wc:

find $dir -name '*.txt' -exec wc -l {} \;

或由 liborm 指定。

dir="."
find $dir -name '*.txt' -exec wc -l {} \; | sort -rn | sed 's/\.txt$//'

不是最优化的方式,因为您可以使用 catechocoreutilsawksed、[=17= 的组合], 等等,但这会得到你想要的:

wc -l ./*.txt | awk 'BEGIN{print "Line\tFile"}1' | sed '$d'

wc -l ./*.txt 将提取行数。 awk 'BEGIN{print "Line\tFile"}1' 将添加 header 标题。 1 对应于标准输入的第一行。 sed '$d' 将打印除最后一行以外的所有行。

示例结果

Line    File
      6 ./test1.txt
      1 ./test2.txt

仅使用 grep -c

的简单性

由于这些问题,我很少在我的脚本中使用 wc -l。我改用 grep -c 。虽然它不如 wc -l 高效,但我们无需担心其他问题,例如摘要行、白色 space 或分叉额外进程。

例如:

/var/log# grep -c '^' *
alternatives.log:0
alternatives.log.1:3
apache2:0
apport.log:160
apport.log.1:196
apt:0
auth.log:8741
auth.log.1:21534
boot.log:94
btmp:0
btmp.1:0
<snip>

单个文件非常简单:

line_count=$(grep -c '^' my_file.txt)

性能比较:grep -c vs wc -l

/tmp# ls -l *txt
-rw-r--r-- 1 root root 721009809 Dec 29 22:09 x.txt
-rw-r----- 1 root root 809338646 Dec 29 22:10 xyz.txt

/tmp# time grep -c '^' *txt

x.txt:7558434
xyz.txt:8484396

real    0m12.742s
user    0m1.960s
sys 0m3.480s

/tmp/# time wc -l *txt
   7558434 x.txt
   8484396 xyz.txt
  16042830 total

real    0m9.790s
user    0m0.776s
sys 0m2.576s

您可以像这样使用 GNU Parallel 非常简洁地解决它(以及许多其他似乎需要 for 循环的问题):

parallel wc -l ::: tmp/*txt

示例输出

   3 tmp/lines.txt
   5 tmp/unfiltered.txt
  42 tmp/file.txt
   6 tmp/used.txt

还有 sed 解决方案!

1。简短快捷

由于总计在最后一行,$d是删除最后一行的命令。

wc -l $directory-path/*.txt | sed '$d'

2。添加 header 行:

wc -l $directory-path/*.txt | sed '$d;1ilines total'

很遗憾,没有对齐。

3。使用对齐方式:将左列格式化为 11 个字符宽度。

wc -l $directory-path/*.txt |
    sed -e '
        s/^ *\([0-9]\+\)/          /;
        s/^ *\([0-9 ]\{11\}\) / /;
        /^ *[0-9]\+ total$/d;
        1i\      lines filename'

会做这份工作

      lines file
          5 ./directory/1.txt
          3 ./directory/2.txt
          2 ./directory/3.txt

4。但是,如果您的 wc 版本真的可以将总计放在第一行:

这个是为了好玩,因为我不相信有 wc 版本把总数放在第一行,但是...

此版本在所有地方删除 total 行,并在输出顶部添加 header 行。

wc -l $directory-path/*.txt |
    sed -e '
        s/^ *\([0-9]\+\)/          /;
        s/^ *\([0-9 ]\{11\}\) / /;
        1{
            /^ *[0-9]\+ total$/ba;
            bb;
           :a;
            s/^.*$/      lines file/
        };
        bc;
       :b;
        1i\      lines file' -e '
       :c;
        /^ *[0-9]\+ total$/d
    '

这更复杂,因为我们不会删除第一行,即使它是 行。

类似于,您也可以使用带有显式分隔符的xargs

ls | xargs -I% wc -l %

然后 xargs 明确不会将所有输入发送到 wc,而是一次发送一个操作数行。

最短答案:

ls | xargs -l wc

这是一份 tailor-made 负责人的工作:

wc -l | head --lines=-1

这样,您仍然可以在一个过程中运行。