计算行数或枚举行号,这样我就可以循环它们--为什么这是反模式? [英] Counting lines or enumerating line numbers so I can loop over them - why is this an anti-pattern?

查看:0
本文介绍了计算行数或枚举行号,这样我就可以循环它们--为什么这是反模式?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我发了下面的代码,被骂了。为什么这是不可接受的?

numberOfLines=$(wc -l <"$1")
for ((i=1; $i<=$numberOfLines; ++$i)); do
  lineN=$(sed -n "$i!d;p;q" "$1")
  # ... do things with "$lineN"
done

我们将输入文件中的行数收集到numberOfLines中,然后从1循环到该数字,在每次迭代中使用sed从文件中取出下一行。

我收到的反馈抱怨说,使用循环内的sed重复读取同一文件以获取下一行的效率很低。我想我可以使用head -n "$i" "$1" | tail -n 1,但这并不能提高效率,不是吗?

有没有更好的方法来做这件事?我为什么要避免此特定方法?

推荐答案

外壳(以及基本上所有高于汇编语言的编程语言)已经知道如何循环遍历文件中的各行;它不需要知道将有多少行来获取下一行--值得注意的是,在您的示例中,sed已经这样做了,所以如果外壳不能做到这一点,您可以循环sed的输出。

循环遍历外壳中文件中的行的正确方法是使用while read。有几个复杂的问题-通常,您重置IFS以避免让外壳不必要地将输入拆分为令牌,并且使用read -r来避免原始Bourne外壳的read实现中带有反斜杠的一些讨厌的遗留行为,这些实现被保留以实现向后兼容。

while IFS='' read -r lineN; do
    # do things with "$lineN"
done <"$1"
除了比sed脚本简单得多之外,它还避免了这样的问题:您读取整个文件一次以获得行数,然后在每次循环迭代中反复读取相同的文件。对于典型的现代磁盘驱动程序,可以通过缓存来避免一些重复读取,但基本事实仍然是,从磁盘读取信息比在可以避免的情况下不读取信息要慢1000倍左右。特别是对于大文件,缓存最终会被填满,因此您最终会一遍又一遍地读入和丢弃相同的字节,从而增加大量的CPU开销,甚至更多的CPU只是在等待磁盘传递您读取的字节时做一些其他的事情。

在外壳脚本中,您还希望尽可能避免外部进程的开销。在紧密循环中数千次调用sed(或者功能相同但更昂贵的双进程head -n "$i"| tail -n 1)将显著增加任何重要输入文件的开销。(另一方面,如果您的循环体可以在sed或Awk中完成,由于read的实现方式,这将比本机外壳while read循环高效得多。这就是为什么while read is also frequently regarded as an antipattern. 并确保您相当熟悉Unix text processing tools-cutpastenlpr等的标准调色板)

sed脚本中的q是一种非常局部的补救措施;您经常会看到一些变体,其中sed脚本每次都会将整个输入文件读到最后,即使它只想从文件中取出最开始的一行。

对于小的输入文件,其影响可以忽略不计,但是仅仅因为它在输入文件小的情况下不会立即造成危害而继续使用这种坏做法是不负责任的。只是不要把这个技巧教给初学者。一点也不。

如果您确实需要显示输入文件中的行数,至少要确保您不会花费大量时间一直查找到最后才能获得该数字。可能是stat文件,并跟踪每行有多少字节,这样您就可以预测剩余的行数(而不是显示类似line 1/approximately 10000000的内容)……或使用外部工具,如pv.

另外,还有一个模糊相关的反模式需要避免;当一次只处理一行时,希望避免将整个文件读入内存。在for循环中这样做还会有一些额外的问题,所以也不要这样做;请参阅https://mywiki.wooledge.org/DontReadLinesWithFor

这篇关于计算行数或枚举行号,这样我就可以循环它们--为什么这是反模式?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆