gab*_*abt 16 shell bash shell-script
我想这可能是一个幼稚的问题,但我无法理解,所以我想问......我正在寻找问题的一些解决方案,当我发现这篇非常有趣的帖子关于为什么[while|for]
在 bash 中使用循环被考虑不好的做法。帖子中有一个很好的解释(请参阅所选答案),但我找不到任何可以解决所讨论问题的内容。
我进行了广泛的搜索:我用谷歌搜索(或duckduckgo-ed)how to read a file in bash
并且我得到的所有结果都指向一个解决方案,根据上述帖子,该解决方案绝对是非 bash 风格并且应该避免的东西。特别是,我们有这个:
while read line; do
echo $line | cut -c3
done
Run Code Online (Sandbox Code Playgroud)
和这个:
for line in `cat file`; do
foo=`echo $line | awk '{print $2}'`
echo whatever $foo
done
Run Code Online (Sandbox Code Playgroud)
这被认为是非常糟糕的 shell 脚本示例。在这一点上,我想知道,这是实际的问题:如果应该避免发布的 while 循环,因为它们是不好的做法等等……我应该怎么做?
编辑:我看到我已经有评论/问题来解决while
循环的确切问题,所以我想扩大一下问题。基本上,我的理解是我需要更深入地研究 bash 命令,这才是我真正应该做的事情。但是,当人们四处搜索时,在一般情况下,人们似乎以不正确的方式使用和教授 bash(根据我的 google-ing)。
ter*_*don 36
您链接到的帖子的重点是解释使用 bash 解析文本文件通常是一个坏主意。它不是专门关于使用循环的,并且在其他上下文中 shell 循环没有本质上的错误。没有人说 shell 脚本while
有点糟糕。另一篇文章说你不应该尝试使用 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
Run Code Online (Sandbox Code Playgroud)
请阅读为什么使用 shell 循环来处理被认为是不好的做法的文本?有关为什么这种事情是个坏主意的详细信息。在这里,我只想澄清一下,那篇文章一般不是反对 shell 循环,而是反对使用 shell 循环(或 shell)来解析文件。
您没有找到使用 bash 执行此操作的更好方法的建议的原因是,没有使用 bash 或任何其他 shell 执行此操作的好方法。无论您做什么,使用 shell 解析文本都会缓慢、繁琐且容易出错。
Shell 主要设计为一种输入要由计算机运行的命令的方式。它们可以用作脚本语言,但同样,它们在给定命令运行时处于最佳状态,而不是用于代替旨在处理文本解析的命令时。
Shell 是工具,就像任何其他工具一样,它们应该用于其设计目的。问题是很多人都学了一点shell脚本,所以他们有了一个工具,一个“锤子”。因为他们只知道一把锤子,所以他们遇到的每个问题对他们来说都像是钉子,他们试图用锤子敲击这颗钉子。可悲的是,解析文本不是 shell 设计用来处理的,它不是“钉子”,所以使用“锤子”并不是一个好主意。
因此,“我应该如何在 bash 中读取文件”的答案非常简单“您不应该使用 bash,而应使用适合该工作的工具”。
cas*_*cas 13
不要使用为每一行调用一次的 shellwhile
或for
循环,只需运行 awk 一次,将文件名作为参数。例如awk
awk '{print "whatever " $2}' file
Run Code Online (Sandbox Code Playgroud)
与cut
:
cut -c3 file
Run Code Online (Sandbox Code Playgroud)
如果您需要在 bash 中对 awk 返回的每一行进行进一步处理,最好的选择是使用命令替换来填充数组。
myarray=( $(awk '{print $2}' file) )
Run Code Online (Sandbox Code Playgroud)
重要的是不要在这里用双引号引用命令替换,因为我们希望shell 发生分词——数组的每个元素都是一个“词”,并且由于 awk 的输入是空格分隔的,它只打印一个字段,它每行将输出一个“单词”。
或者,您可以将 bash 内置readarray
akamapfile
与进程替换一起使用:
mapfile -t myarray < <(awk '{print $2}' file)
Run Code Online (Sandbox Code Playgroud)
的mapfile
/readarray
如果输入包含glob模式就像是必需的变种*
在2 $,否则shell会尝试展开水珠。
将数据放入数组后,您可以使用 for 循环对其进行迭代,例如:
for i in "${myarray[@]}"; do do_something_with "$i"; done
Run Code Online (Sandbox Code Playgroud)
或将其作为 args 传递给另一个程序或内置程序:
printf "whatever %s\n" "${myarray[@]}"
Run Code Online (Sandbox Code Playgroud)
但是请注意,在 awk 中进行任何额外处理几乎总是更好。这可能意味着重新设计和重新编写您的 bash 脚本,以便大部分工作在 awk 中完成。或者,如果事实证明不需要 bash,则将整个内容重写为 awk 脚本。perl 也是如此。和蟒蛇。和其他语言。
shell 是一种很好的语言,用于编排其他程序来处理数据和执行实际工作,但在处理数据本身方面却很糟糕——几乎任何其他语言都比 shell 处理数据更好。
如果您发现自己在 shell 和 awk 或其他语言之间来回移动数据,这是一个好兆头,表明您需要用 awk(或其他语言)重写整个过程。
Aus*_*arn 13
在您的示例中要避免的不是循环,而是多次调用命令的无意义使用。碰巧的是,循环是 shell 脚本中命令无用调用的最常见原因之一(另一个重要原因是不记得只使用重定向)。
启动一个新进程是几乎所有系统上可能的最昂贵的操作之一,因此高效的脚本(以及通常的高效代码)将进程总数保持在最低限度。这种效率限制是为什么inetd
已经失宠的很大一部分原因,以及为什么许多 Web 服务器默认启动一堆长期存在的进程并根据需要将连接传递给它们,而不是按需为每个连接生成一个进程。
您的两个示例都可以简化为为整个操作启动单个流程。因此,第一个将成为:
cut -c3
Run Code Online (Sandbox Code Playgroud)
第二个是:
awk '{print $2}'` < file
Run Code Online (Sandbox Code Playgroud)
这些不仅效率更高,而且可读性更强。
这并不是说循环一般是不好的,只是你可能在其他语言中使用它的很多东西在 shell 脚本中不需要它,因为所涉及的工具固有地处理多行或文件。的东西一个很好的例子这将是有效的使用它在做什么(假设“东西”不支持固有的重试)被处理多次尝试的。
小智 5
其他答案是正确的,我只是想知道他们所传达的概念是否很难理解。所以我想给你一个比喻。希望这是有道理的:
您可以将 shell 脚本视为导体。指挥指向不同的音乐家,指示他们应该开始或停止演奏,演奏得更大声或更轻柔,更快或更慢。指挥本身并没有下到管弦乐队开始演奏乐器;指挥也不会通过告诉音乐家如何翻页来“微观管理”音乐家。指挥相信音乐家可以自己完成这项工作。指挥只是指导音乐家何时演奏以及如何演奏。
你的shell脚本是这样的。它正在编排许多其他命令。它根本不直接操作文件。即使您使用像mv
或 之类的命令cp
,这些命令也正在执行实际工作。他们就像音乐家。shell 脚本对mv
“音乐家”说——把它移到这里——它就这样做了。shell 脚本不会移动文件本身。它让mv
命令来做。
此外,就像指挥不会告诉音乐家何时翻页一样,shell 脚本不需要将文件一次一行地提供给命令。它可以将整个文件交给命令并告诉它如何处理它。因此不需要循环。循环不应该用于对命令进行微观管理,它应该用于在多个文件上编排多个命令。
不要忘记,shell 脚本中的“命令”与其他语言中的命令不同——它们不是构成 shell 脚本语言的关键字。相反,它们都是可以从 shell 运行的独立程序。cp
,例如是它自己的程序。该程序复制文件。它有自己的手册和可以传递给它的参数列表。您可以cp
从 shell(在脚本之外)运行,而您所做的只是调用一个名为cp
. 以同样的方式,您可以创建自己的程序(通过创建 shell 脚本)并从您的 shell 脚本中调用它们。
希望有助于解释一下。