使用shell循环来处理文本内容的负面影响
1. 概述
在本文中,我们将讨论使用 shell 循环处理文本内容的负面影响。首先,我们将通过示例介绍 shell 循环如何处理字符串和文本文件背后的概念。之后,我们将介绍使用 shell 循环处理文本数据的负面影响。
2. 概念
当我们用 C 或 Python 等语言编写程序时,我们有多种方法来完成一项任务。同样,在 shell 脚本中,我们可以以各种方式自动化给定任务。但是,这并不意味着每种方法都有效。
大多数 Linux 新手都会尝试将其他语言(如 C 和 Python)的概念投射到 shell 脚本中。正因为如此,他们通常最终会编写“糟糕的代码”,这可以更有效地完成——有时,使用更少的代码。
例如,让我们看一个例子:
while read line; do
echo $line | cut -c3
done
该代码基本上适用于每个文本行,然后打印出字符串中的第三个字符。几行就可以了。但是当我们有一百万行时会发生什么?**我们会在百万文本行上单独使用剪切二进制文件吗? **我们会解决的。
2.1. 为什么使用 Shell 脚本?
shell 的工作只是执行命令。当我们收集一堆逻辑相关的命令并将它们放在一个源文件中时,我们创建了一组可以反复使用的有组织的活动。这提高了生产力,让生活更轻松。
**Shell 只解释我们给它的命令。**这些命令可以是内置的,也可以只是用 C 等高级语言编写的独立二进制文件。
另一方面,shell 是一种高级语言(有些人可能会争辩说它甚至不是一种语言),它是执行实际工作的命令。shell 仅用于编排这些命令。
命令编排是通过使用管道、流和重定向等 shell 特性来完成的。这些 shell 功能非常方便,以至于即使过了半个世纪,我们仍然使用 shell。因此,我们只需编写一组命令,让 shell 调用它们,shell 让它们按照自己的节奏工作。
在 shell 中,我们为给定任务调用尽可能少的命令。为什么?因为调用命令是有代价的,我们将在下一节中介绍。
3. 性能问题
3.1. 幕后花絮
**在 shell 中,当我们调用外部命令时,很多事情都会在幕后发生。**命令二进制文件被加载到内存中,并创建和初始化一个进程。然后,为一个简单的任务执行数百条指令。最后,进程必须被销毁并从内存中清除。
现在,想象一下如果我们有一个包含一百万行的文本文件,我们在上面执行这个块:
while IFS= read line; do
echo $line | cut -c3
done < file
这意味着我们称经过上述过程一百万次的*剪切二进制文件。*所有这些只是从每一行中提取一个字符。这是糟糕的代码,不是吗?
3.2. Shell 命令不是 UNIX 实用程序
C 等编程语言中的函数通常在单个进程中运行。标准库跟踪其内存空间和内部缓冲区,以避免昂贵的系统调用。
相反,当我们运行像**read 或echo 这样的内置 shell 时,会为该命令创建一个单独的进程。因此,它们不共享共同的记忆。**
例如,read命令仅用于读取单行。如果它读取了换行符,那么下一个命令将错过它,因为没有缓冲区来跟踪它。因此,下一个命令必须等待read完成。
因此,shell 中的命令是按顺序运行的,我们必须等待它们。
3.3. 改用内置的 Shell
大多数 shell,如bash 和zsh ,都提供内置功能来执行许多常见任务。与外部二进制文件相比,运行 shell 内置程序非常高效。
例如,让我们重写上面的代码,但让我们使用内置的 shell 而不是cut:
while IFS= read line; do
echo ${line:2:1}
done < file
**Bash 上的先前代码与先前代码之间的性能比约为 1:600 — 一分钟与十分钟。**那是巨大的!
同样,也可以使用内置的 shell 编写以下代码:
while IFS= read line; do
echo "Length: $(wc -c $line)"
done < file
while IFS= read line; do
echo "Length: ${#line}"
done < file
4. 尽可能避免循环
正如我们在上面看到的,在 shell 中执行给定任务总是有更有效的方法。为问题找到更有效的解决方案可能需要时间,但从长远来看会有所回报。
例如,让我们使用一个循环来处理一个包含大约 30,000 行的巨大文本文件。我们将打印每行的第一个字符:
#!/bin/bash
while IFS= read -r line; do
echo "$line" | head -c1
done < data.json
现在,我们将使用time 命令来测试它所花费的时间:
$ time ./extract.sh
...
...
./extract.sh 36.96s user 15.43s system 97% cpu 53.834 total
仅仅处理这个文件就需要大量的时间。但是,如果我们只是省略循环并使用简单的awk 命令,则需要 0 秒:
$ time awk '{print substr($0,0,1)}' data.json
awk '{print substr($0,0,1)}' data.json 0.01s user 0.06s system 89% cpu 0.081 total
正如我们所看到的,当我们可以更有效地完成相同的任务时,不需要使用循环。然而,这并不意味着我们应该避免使用循环。有时,我们需要针对特定任务的循环,例如向 Web 服务器发出多个后续请求。