如何解决计算行数或枚举行号,以便我可以遍历它们 - 为什么这是一种反模式?
我贴了下面的代码被骂了。为什么这是不可接受的?
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
但这几乎没有效率更高,是吗?
解决方法
shell(基本上所有高于汇编语言的编程语言)已经知道如何遍历文件中的行;它不需要知道将有多少行来获取下一行——令人惊讶的是,在你的例子中,sed
已经这样做了,所以如果 shell 不能这样做,你可以循环从sed
代替。
在 shell 中循环文件中的行的正确方法是使用 while read
。有一些复杂情况 - 通常,您重置 IFS
以避免 shell 不必要地将输入拆分为标记,并且您使用 read -r
来避免在原始 Bourne shell 的实现中使用反斜杠的一些讨厌的遗留行为的 read
,为了向后兼容而保留。
while IFS='' read -r lineN; do
# do things with "$lineN"
done <"$1"
除了比您的 sed
脚本简单得多之外,这还避免了您读取整个文件一次以获取行数,然后在每次循环迭代中一次又一次地读取同一个文件的问题。使用典型的现代磁盘驱动程序,可以通过缓存来避免一些重复读取,但基本事实仍然是,从磁盘读取信息的速度比不这样做时可以避免它慢 1000 倍。特别是对于一个大文件,缓存最终会填满,所以你最终会一遍又一遍地读入和丢弃相同的字节,增加大量的 CPU 开销,甚至更多的 CPU 只是在做其他事情时等待磁盘传送您读取的字节。
在 shell 脚本中,如果可以,您还希望避免外部进程的开销。在紧密循环中调用 sed
(或功能等效但成本更高的两个进程 head -n "$i"| tail -n 1
)数千次将为任何非平凡的输入文件增加大量开销。 (另一方面,如果您的循环体可以在例如 sed
或 Awk 中完成,那么这将比本机 shell while read
循环更有效,因为 { {1}} 已实现。这就是为什么 while read
is also frequently regarded as an antipattern.)
read
脚本中的 q
是一种非常局部的补救措施;您经常会看到 sed
脚本每次都会读取整个输入文件直到最后的变化,即使它只想从文件中取出最开始的一行。
对于一个小的输入文件,影响可以忽略不计,但是仅仅因为当输入文件很小时它不会立即有害而延续这种不良做法是不负责任的。只是不要将这种技术教给初学者。完全没有。
如果你真的需要显示输入文件中的行数,至少确保你不要为了获得那个数字而花费大量时间寻找到最后。也许 sed
文件并跟踪每行有多少字节,因此您可以预测剩余的行数(而不是 stat
显示类似 line 1/10345234
? ) ... 或使用像 pv
.
顺便说一下,你也想避免一个模糊相关的反模式;当您一次只处理一行时,您希望避免将整个文件读入内存。在 line 1/approximately 10000000
循环中这样做也有一些额外的问题,所以也不要这样做;见https://mywiki.wooledge.org/DontReadLinesWithFor
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。