在一个文件夹中,我想打印每个包含.txt
行或更少行的n=27
文件的名称。我能做
wc -l *.txt | awk '{if ($1 <= 27){print}}'
问题在于文件夹中的许多文件都是数百万行(并且这些行很长),因此命令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)
}
可以称为
awk -v n=27 -f myAwk.awk *.txt
但是,代码无法打印出完全空白的文件。我不确定该如何解决,也不确定要使用的awk脚本。
答案 0 :(得分:8)
对于下一个文件和ENDFILE,使用GNU awk:
awk -v n=27 'FNR>n{f=1; nextfile} ENDFILE{if (!f) print FILENAME; f=0}' *.txt
任何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
无论输入文件是否为空,这两者都可以工作。非gawk版本的警告与您当前其他awk答案的警告相同:
awk 'script' foo bar foo
),而您希望它显示多次,并且awk 'script' foo FS=, bar
)gawk版本没有此类限制。
更新:
要测试上述GNU awk脚本和the GNU grep+sed script posted by xhienne之间的时间间隔,因为她说她的解决方案是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} }'
然后在它们上运行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
两个脚本都产生相同的输出文件。上面是在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
要在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
答案 1 :(得分:4)
如果您使用的是GNU grep
(不幸的是MacOSX> = 10.8提供了BSD grep,其-m
和-c
选项act globally,而不是每个文件),您可能会发现另一种有趣的方法(比纯awk
脚本还快):
grep -c -m28 -H ^ *.txt | sed '/:28$/ d; s/:[^:]*$//'
说明:
grep -c -m28 -H ^ *.txt
输出每个文件的名称以及每个文件的行数,但读取的行数不得超过28行sed '/:28$/ d; s/:[^:]*$//'
删除至少包含28行的文件,并打印其他文件的文件名备用版本:顺序处理而不是并行处理
res=$(grep -c -m28 -H ^ $files); sed '/:28$/ d; s/:[^:]*$//' <<< "$res"
爱德华·莫顿(Ed Morton)质疑我的说法,即该答案可能比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
结果:
结论:
在撰写本文时,在类似于OP机器的常规Unix多核笔记本电脑上,此答案是最快的,可提供准确结果的答案。在我的机器上,它的速度是最快的awk脚本的两倍。
注意:
平台为何重要?因为我的答案依赖于grep
和sed
之间的并行处理。当然,为了获得公正的结果,如果您只有一个CPU内核(VM?)或操作系统在CPU分配方面的其他限制,则应该对备用(顺序)版本进行基准测试。
很显然,您不能仅就挂墙时间得出结论,因为它取决于请求CPU的并发进程数与计算机上的内核数。因此,我添加了user + sys计时
这些时间平均超过20次运行,但命令花费的时间超过1分钟(仅一次运行)除外
对于所有少于10秒的答案,shell处理*.txt
所花费的时间不可忽略,因此我对文件列表进行了预处理,将其放入变量中,并附加了我正在基准测试的命令的变量内容。
所有答案给出的结果均相同。1.三元组的答案的结果中包含argv[0]
(“ awk”)(在我的测试中已固定); 2. kvantour的答案,其中仅列出了空文件(以-v n=27
固定);和3.找不到空文件的find + sed答案(不固定)。
我手头没有GNU sed 4.5,因此无法测试ctac_'s answer。这可能是所有方法中最快的,但也会丢失空文件。
python答案不会关闭其文件。我必须先做ulimit -n hard
。
答案 2 :(得分:3)
您可以尝试将awk
移到下一个文件,只要行数超过27
:
awk -v n=27 'BEGIN{for (i=1; i<ARGC; i++) f[ARGV[i]]}
FNR > n{delete f[FILENAME]; nextfile}
END{for (i in f) print i}' *.txt
awk
逐行处理文件,因此它不会尝试读取完整的文件来获取行数。
答案 3 :(得分:3)
这怎么样?
awk 'BEGIN { for(i=1;i<ARGC; ++i) arg[ARGV[i]] }
FNR==28 { delete arg[FILENAME]; nextfile }
END { for (file in arg) print file }' *.txt
我们将文件名参数列表复制到关联数组,然后从中删除所有具有第28行的文件。空文件显然不符合此条件,因此最后,我们剩下的所有行较少的文件,包括空行。
nextfile
是许多Awk变体的通用扩展,然后在POSIX于2012年进行了编纂。 /或尝试使用GNU Awk。
答案 4 :(得分:3)
虽然awk似乎是最有趣的方式,但是triplee,anubhava和Ed Morton的解决方案又是另外一种。在三元组和anubhava解决方案的哪里使用output.write(version, sizeof(VERSION_NR));
语句,而Ed Morton的POSIX证明解决方案正在读取完整文件,而我提供的解决方案是不读取完整文件。
nextfile
答案 5 :(得分:1)
with sed(GNU sed)4.5:
sed -n -s '28q;$F' *.txt
答案 6 :(得分:1)
您可以在一些bash内联脚本的帮助下使用find
:
find -type f -exec bash -c '[ $(grep -cm 28 ^ "${1}") != "28" ] && echo "${1}"' -- {} \;
命令[ $(grep -cm 28 ^ "${1}") != "28" ] && echo "${1}"
使用grep最多搜索28行(^
)。如果该命令返回!=“ 28”,则文件必须少于28行。
答案 7 :(得分:0)
如果必须单独调用awk,请要求它在第28行停止:
for f in ./*.txt
do
if awk 'NR > 27 { fail=1; exit; } END { exit fail; }' "$f"
then
printf '%s\n' "$f"
fi
done
awk变量的默认值为零,因此,如果我们从不执行第28行,则退出代码为零,从而使if
测试成功,并因此打印文件名。
答案 8 :(得分:0)
python -c "import sys; print '\n'.join([of.name for of in [open(fn) for fn in sys.argv[1:]] if len(filter(None, [of.readline() for _ in range(28)])) <= 27])" *.txt
答案 9 :(得分:0)
软件工具和 GNU sed
( v4.5 之前的旧版本)混搭:
find *.txt -print0 | xargs -0 -L 1 sed -n '28q;$F'
这会丢失0字节的文件,以包括这些文件,请这样做:
find *.txt \( -exec sed -n '28{q 1}' '{}' \; -or -size 0 \) -print
(由于某种原因,通过sed
运行-exec
比xargs
慢 12%。)
sed
代码从ctac's answer被盗。
注意:在我系统较旧的sed
v4.4-2 上,q
uit 命令与{{1} } switch不仅退出当前文件,而且还完全退出--separate
。这意味着每个文件都需要一个sed
的单独实例。