我想这可能是一个天真的问题,但我无法理解所以我想问......我正在寻找问题的解决方案,当我发现这个非常有趣的帖子为什么[while|for]
在 bash 中使用循环被认为是不好的做法。帖子中有一个很好的解释(请参阅所选答案),但我找不到任何可以解决所讨论问题的内容。
我进行了广泛的搜索:我用谷歌搜索(或 duckduckgo-ed)how to read a file in bash
,我得到的所有结果都指向一个解决方案,根据上述帖子,该解决方案绝对是非 bash 风格,是应该避免的。特别是,我们有这个:
while read line; do
echo $line | cut -c3
done
和这个:
for line in `cat file`; do
foo=`echo $line | awk '{print $2}'`
echo whatever $foo
done
这些被认为是非常糟糕的 shell 脚本示例。此时我想知道,这就是实际的问题:是否应该避免发布的 while 循环,因为它们是不好的做法等等......我应该做什么?
编辑:我发现我已经有评论/问题来解决循环的确切问题while
,所以我想稍微扩大一下问题的范围。基本上,我的理解是我需要更深入地研究 bash 命令,这才是我真正应该做的事情。但是,当人们四处搜索时,看起来人们在一般情况下以不正确的方式使用和教授 bash(根据我的谷歌搜索)。
答案1
您链接到的帖子的要点是解释这一点一般来说,使用 bash 解析文本文件是一个坏主意。它并不是专门关于使用循环的,并且在其他上下文中 shell 循环没有本质上的错误。没有人说 shell 脚本有while
什么不好。另一篇文章说您不应该尝试使用 shell 解析文本文件,而应该使用其他工具。
为了澄清,当我说“使用 shell”时,我的意思是使用 shell 的内部工具来打开文件、提取数据并解析它。例如这样的事情:
while read number; do
if [ $number -gt 10 ]; then
echo "The number '$number' is greater than 10"
else
echo "The number '$number' is less than or equal to 10"
done < numbers.txt
请阅读答案为什么使用 shell 循环处理文本被认为是不好的做法?详细了解为什么这种事情是一个坏主意。在这里,我只是澄清一下,这篇文章一般来说并不是反对 shell 循环,而是反对使用 shell 循环(或 shell)来解析文件。
您没有找到使用 bash 执行此操作的更好方法的建议的原因是,没有使用 bash 或任何其他 shell 执行此操作的好方法。无论您做什么,使用 shell 解析文本都会很慢、很麻烦并且容易出错。
Shell 主要设计为输入计算机运行命令的一种方式。它们可以用作脚本语言,但同样,当给定要运行的命令时,它们才能发挥最佳作用。不是使用时反而旨在处理文本解析的命令。
Shell 是工具,就像任何其他工具一样,它们应该用于其设计目的。问题是,很多人都学过一点shell脚本,所以他们就有了一个工具,一把“锤子”。因为他们只知道一把锤子,所以他们遇到的每个问题对他们来说都像是一颗钉子,他们尝试用锤子敲这颗钉子。遗憾的是,解析文本并不是 shell 设计来处理的,它不是“钉子”,因此使用“锤子”并不是一个好主意。
因此,“我应该如何在 bash 中读取文件”的答案非常简单,“你不应该使用 bash,而应该使用适合该工作的工具”。
答案2
在您的示例中要避免的不是循环,而是无意义地使用多次调用命令。恰好循环是 shell 脚本中命令无用调用的最常见原因之一(另一个重要原因是不记得只使用重定向)。
启动新进程是几乎所有系统上最昂贵的操作之一,因此高效的脚本(以及通常的高效代码)可以将进程总数保持在最低限度。这种效率限制是inetd
它失宠的主要原因,也是为什么许多 Web 服务器默认启动一堆长期存在的进程并根据需要向它们提供连接,而不是按需为每个连接生成一个进程。
你的两个例子都可以简化为开始一个单身的整个操作的流程。第一个因此将变为:
cut -c3
第二个是:
awk '{print $2}'` < file
这些不仅更高效,而且更具可读性。
这并不是说循环通常不好,只是说您在其他语言中可能使用循环执行的许多操作在 shell 脚本中不需要它,因为所涉及的工具本质上处理多行或多文件。一个很好的例子会使用它的有效目的是处理做某事的多次尝试(假设“某事”本质上不支持重试)。
答案3
而不是使用调用的 shellwhile
或for
循环awk
每行一次,只需运行 awk 一次,并将文件名作为参数。例如
awk '{print "whatever " $2}' file
cut
与:相同
cut -c3 file
如果您需要做进一步处理在bash中在 awk 返回的每一行上,最好的选择是使用命令替换填充数组。
myarray=( $(awk '{print $2}' file) )
重要的是不是在这里用双引号引用命令替换,因为我们想shell 会进行分词 - 数组的每个元素将是一个“单词”,并且由于 awk 的输入是以空格分隔的并且它只打印一个字段,因此每行将输出一个“单词”。
或者,您可以使用内置的readarray
bashmapfile
以及流程替代:
mapfile -t myarray < <(awk '{print $2}' file)
如果输入包含像 $2 这样的 glob 模式,则需要mapfile
/变体,否则 shell 将尝试扩展 glob。readarray
*
将数据放入数组后,您可以使用 for 循环对其进行迭代,例如:
for i in "${myarray[@]}"; do do_something_with "$i"; done
或将其作为参数传递给另一个程序或内置程序:
printf "whatever %s\n" "${myarray[@]}"
但请注意,在 awk 中进行任何额外处理几乎总是会更好。这可能意味着重新设计和重写您的 bash 脚本,以便大部分工作在 awk 中完成。或者,如果事实证明不需要 bash,则将整个内容重写为 awk 脚本。 Perl 也是如此。和蟒蛇。和其他语言。
shell 是一种很好的编排语言其他程序可以处理数据并完成实际工作,但在执行数据处理工作本身方面却很糟糕 - 几乎任何其他语言都比 shell 更适合处理数据。
如果您发现自己在 shell 和 awk 或其他语言之间来回移动数据,那么这是一个好迹象,表明您需要用 awk(或其他语言)重写整个内容。
答案4
我是从务实的角度写这个答案的。我不认为在 Bash 中使用循环就一定是坏事。
使用 Bash 等脚本语言的一大好处是尽可能快速、轻松地实现您想到的任务。 Bash 通过让您能够通过管道组合小型、高效、通用的程序(例如 cat、cut、head、grep、tee 等)来实现这一点。人们更喜欢使用 Bash 单行代码来利用一组已经很高效的程序(例如grep <something> input-file | cut -c3
),而不是从头开始编写一个执行相同工作的程序,大多数情况下甚至会更慢。
如果你大致了解 Bash 可以为你提供什么,这意味着你了解足够多的基本 unix 程序、Bash 的管道特性和编程语言结构,并且如果使用循环仍然很方便,那就去做吧。