列出包含"n"或更少行的文件

Rem*_*i.b 14 bash shell performance awk file

在一个文件夹中,我想打印.txt包含n=27行或更少行的每个文件的名称.我可以

wc -l *.txt | awk '{if ($1 <= 27){print}}'
Run Code Online (Sandbox Code Playgroud)

问题是该文件夹中的许多文件是数百万行(并且行很长),因此命令wc -l *.txt非常慢.原则上,进程可以计算行数,直到找到至少n行,然后继续下一个文件.

什么是更快的替代方案?

仅供参考,我在 MAC OSX 10.11.6

尝试

这是一次尝试 awk

#!/bin/awk -f

function printPreviousFileIfNeeded(previousNbLines, previousFILENAME)
{
  if (previousNbLines <= n) 
  {
    print previousNbLines": "previousFILENAME
  }
}

BEGIN{
  previousNbLines=n+1
  previousFILENAME=NA
} 


{
  if (FNR==1)
  {
    printPreviousFileIfNeeded(previousNbLines, previousFILENAME)
    previousFILENAME=FILENAME
  }
  previousNbLines=FNR
  if (FNR > n)
  {
    nextfile
  }
}

END{
  printPreviousFileIfNeeded(previousNbLines, previousFILENAME)
}
Run Code Online (Sandbox Code Playgroud)

可称为

awk -v n=27 -f myAwk.awk *.txt
Run Code Online (Sandbox Code Playgroud)

但是,代码在打印完全空文件时失败.我不知道如何解决这个问题,我不确定我的awk脚本是否可行.

Ed *_*ton 8

使用GNU awk for nextfile和ENDFILE:

awk -v n=27 'FNR>n{f=1; nextfile} ENDFILE{if (!f) print FILENAME; f=0}' *.txt
Run Code Online (Sandbox Code Playgroud)

有任何awk:

awk -v n=27 '
    { fnrs[FILENAME] = FNR }
    END {
        for (i=1; i<ARGC; i++) {
            filename = ARGV[i]
            if ( fnrs[filename] < n ) {
                print filename
            }
        }
    }
' *.txt
Run Code Online (Sandbox Code Playgroud)

无论输入文件是否为空,这些都将起作用.非gawk版本的注意事项与您当前的其他awk答案相同:

  1. 它依赖于多次出现的相同文件名(例如awk 'script' foo bar foo),并且您希望它多次显示,并且
  2. 它依赖于arg列表中没有设置变量(例如awk 'script' foo FS=, bar)

gawk版本没有这样的限制.

更新:

为了测试上面的GNU awk脚本和xhienne发布的GNU grep + sed脚本之间的时间,因为她说她的解决方案是faster than a pure awk script我用这个脚本创建了10,000个输入文件,全长0到1000行:

$ awk -v numFiles=10000 -v maxLines=1000 'BEGIN{for (i=1;i<=numFiles;i++) {numLines=int(rand()*(maxLines+1)); out="out_"i".txt"; printf "" > out; for (j=1;j<=numLines; j++) print ("foo" j) > out} }'
Run Code Online (Sandbox Code Playgroud)

然后对它们运行2个命令并得到这些第3次运行时序结果:

$ time grep -c -m28 -H ^ *.txt | sed '/:28$/ d; s/:[^:]*$//' > out.grepsed

real    0m1.326s
user    0m0.249s
sys     0m0.654s

$ time awk -v n=27 'FNR>n{f=1; nextfile} ENDFILE{if (!f) print FILENAME; f=0}' *.txt > out.awk

real    0m1.092s
user    0m0.343s
sys     0m0.748s
Run Code Online (Sandbox Code Playgroud)

两个脚本都生成相同的输出文件.以上是在cygwin上的bash中运行的.我希望在不同的系统上,时序结果可能会略有不同,但差异总是可以忽略不计.


每行打印10行最多20个随机字符(请参阅注释):

$ maxChars=20
    LC_ALL=C tr -dc '[:print:]' </dev/urandom |
    fold -w "$maxChars" |
    awk -v maxChars="$maxChars" -v numLines=10 '
        { print substr($0,1,rand()*(maxChars+1)) }
        NR==numLines { exit }
    '
0J)-8MzO2V\XA/o'qJH
@r5|g<WOP780
^O@bM\
vP{l^pgKUFH9
-6r&]/-6dl}pp W
&.UnTYLoi['2CEtB
Y~wrM3>4{
^F1mc9
?~NHh}a-EEV=O1!y
of
Run Code Online (Sandbox Code Playgroud)

要在awk中完成所有操作(这会慢得多):

$ cat tst.awk
BEGIN {
    for (i=32; i<127; i++) {
        chars[++charsSize] = sprintf("%c",i)
    }
    minChars = 1
    maxChars = 20
    srand()
    for (lineNr=1; lineNr<=10; lineNr++) {
        numChars = int(minChars + rand() * (maxChars - minChars + 1))
        str = ""
        for (charNr=1; charNr<=numChars; charNr++) {
            charsIdx = int(1 + rand() * charsSize)
            str = str chars[charsIdx]
        }
        print str
    }
}

$ awk -f tst.awk
Heer H{QQ?qHDv|
Psuq
Ey`-:O2v7[]|N^EJ0
j#@/y>CJ3:=3*b-joG:
?
^|O.[tYlmDo
TjLw
`2Rs=
!('IC
hui
Run Code Online (Sandbox Code Playgroud)


xhi*_*nne 5

如果您使用 GNU grep(不幸的是 MacOSX >= 10.8 提供了 BSD grep ,其-m-c选项全局作用,而不是每个文件),您可能会发现这个替代方案很有趣(并且比纯awk脚本更快):

grep -c -m28 -H ^ *.txt | sed '/:28$/ d; s/:[^:]*$//'
Run Code Online (Sandbox Code Playgroud)

解释:

  • grep -c -m28 -H ^ *.txt输出每个文件的名称以及每个文件中的行数,但读取的行数永远不会超过 28 行
  • sed '/:28$/ d; s/:[^:]*$//'删除至少有 28 行的文件,并打印其他文件的文件名

替代版本:顺序处理而不是并行处理

res=$(grep -c -m28 -H ^ $files); sed '/:28$/ d; s/:[^:]*$//' <<< "$res"
Run Code Online (Sandbox Code Playgroud)

标杆管理

埃德·莫顿对我的说法提出质疑,即这个答案可能比awk。他在他的答案中添加了一些基准,尽管他没有给出任何结论,但我认为他发布的结果具有误导性,显示了我的答案的更大的挂钟时间,而不考虑用户和系统时间。因此,这是我的结果。

首先是测试平台:

  • 一台运行 Linux 的四核 Intel i5 笔记本电脑,可能非常接近 OP 的系统(Apple iMac)。

  • 一个全新的目录,包含 100.000 个文本文件,平均约 400 行,总共 640 MB,完全保存在我的系统缓冲区中。这些文件是使用以下命令创建的:

    for ((f = 0; f < 100000; f++)); do echo "File $f..."; for ((l = 0; l < RANDOM & 1023; l++)); do echo "File $f; line $l"; done > file_$f.txt; done
    
    Run Code Online (Sandbox Code Playgroud)

结果:

结论:

在撰写本文时,在类似于 OP 机器的常规 Unix 多核笔记本电脑上,这个答案是给出准确结果的最快的。在我的机器上,它的速度是最快的 awk 脚本的两倍。

笔记:

  • 为什么平台很重要?grep因为我的答案依赖于和之间的并行处理sed。当然,为了获得公正的结果,如果您只有一个 CPU 核心(VM?)或操作系统在 CPU 分配方面存在其他限制,您应该对备用(顺序)版本进行基准测试。

  • 显然,您不能仅根据挂起时间得出结论,因为它取决于请求 CPU 的并发进程数与机器上的内核数。因此我添加了用户+系统计时

  • 这些计时是 20 次运行的平均值,除了命令花费超过 1 分钟的时间(仅运行一次)

  • 对于所有耗时不到 10 秒的答案,shell 处理所花费的时间*.txt不可忽略,因此我预处理了文件列表,将其放入变量中,并将变量的内容附加到我正在基准测试的命令中。

  • 所有答案都给出了相同的结果,除了 1. Tripleee 的答案,argv[0]其结果中包含(“awk”)(在我的测试中修复);2. kvantour 的答案仅列出了空文件(用 修复-v n=27);3. find+sed 答案缺少空文件(未修复)。

  • 我无法测试ctac_ 的答案,因为我手头没有 GNU sed 4.5。它可能是最快的,但也会丢失空文件。

  • python 答案不会关闭其文件。我必须先做ulimit -n hard