如何在Bash中添加一列数字
1. 概述
在本教程中,我们将学习如何在 Bash shell 中将一列数字相加。我们将仔细研究一些可用于此目的的 Bash 实用程序。我们还将对所提供解决方案的性能进行基准测试。
2. 设置
首先,让我们设置我们将在大部分教程中使用的输入文件:
$ for i in `seq 1000000`; do echo $(($RANDOM%100)); done >numbers.csv
在这里,我们正在生成一个文件numbers.csv,其中包含 1-100 范围内的一百万个随机数。我们使用seq 命令运行一个for循环,使用RANDOM 内置变量生成 1,000,000 个数字。
在接下来的部分中,我们还将查看使用time 命令提供的解决方案的本地速度,以了解每个命令的执行方式。
3. 使用awk工具
让我们从awk 命令开始计算列中数字的总和:
$ awk '{Total=Total+$1} END{print "Total is: " Total}' numbers.csv
Total is: 49471228
现在,让我们看看使用time 命令的执行时间:
$ time awk '{Total=Total+$1} END{print "Total is: " Total}' numbers.csv
Total is: 49471228
real 0m0.228s
user 0m0.141s
sys 0m0.047s
它非常快!我们可以在 0.228 秒内计算出一百万个数字的总和。事实上,awk是 Bash 中用于文件处理的最强大的工具之一。
3.1. 当文件包含多列时
到目前为止,我们知道一种使用awk将列中的数字相加的方法。让我们看看我们在一个文件中有多个列并且我们只对计算特定列的总和感兴趣的情况:
$ cat prices.csv
Books,40
Bag,70
Dress,80
Box,10
此处,文件prices.csv包含两列。现在,让我们计算第二列中元素的总和:
$ awk -F "," '{Total=Total+$2} END{print "Total is: " Total}' prices.csv
Total is: 200
3.2. 当文件包含标题行时
有时,文本或 CSV 文件也包含标题行。该标题行通常包含列名,以提高可读性。让我们修改我们的prices.csv并添加一个标题行:
$ cat prices.csv
Item,Value
Books,40
Bag,70
Dress,80
Box,10
当文件包含标题行时,我们希望在文本处理发生之前消除此标题行。有几种方法 可以实现这一点。在这种情况下,我们将使用awk 工具来忽略标题行。所以,让我们继续修改我们的命令来计算列总和:
$ awk -F "," 'NR!=1{Total=Total+$2} END{print "Total is: " Total}' prices.csv
Total is: 200
在接下来的部分中,我们将检查一些其他方法来将列中的数字相加,并评估awk解决方案相对于这些方法的执行情况。
4. 使用 Bash 循环进行迭代
awk是一个很棒的工具,但是,我们也可以使用循环 来遍历列值。
4.1. 使用expr命令
让我们运行一个实验并检查expr 命令在for循环中计算总和的有效性:
$ time (sum=0;for number in `cat numbers.csv`; do sum=`expr $sum + $number`; done; echo "Total is: $sum")
Total is: 49471228
real 212m48.418s
user 7m19.375s
sys 145m48.203s
**处理速度非常慢。使用expr命令,添加一百万个数字需要 3.5 多个小时。**值得注意的是,expr实用程序是 Bash 早期的遗留物,我们应该只在我们的脚本需要与遗留(POSIX 之前)实现互操作的情况下使用它。
4.2. 使用算术展开
由于使用expr命令没有多大帮助,让我们尝试另一种使用算术扩展的方法:
$ time (sum=0;for number in `cat numbers.csv`; do sum=$((sum+number)); done; echo "Total is: $sum")
Total is: 49471228
real 0m1.961s
user 0m1.813s
sys 0m0.125s
在这里,我们使用算术展开式计算总和,形式为$((..))。与expr*命令相反,使用算术扩展,我们能够在两秒内添加一百万个数字*。算术扩展允许我们执行简单的整数算术。但是,它不适用于浮点数。因此,对于浮点运算,我们必须使用bc 命令。我们将在下一节检查bc命令的实现。
5. 使用bc命令添加值
bc命令对单行表达式执行计算。因此,我们需要将这些数字组合成一行,并由加法运算符分隔。然后我们将表达式传递给bc以计算总数。让我们来看看实现这一点的几种方法。
5.1.使用paste命令
首先,**让我们看一下将数据集的前 10 个数字排列在一行中的paste **命令,它们之间使用加号 (+) 运算符:
$ cat numbers.csv| head -10 | paste -sd+ -
2+44+6+15+23+0+15+88+82+1
选项*-s确保 paste 将所有数字连接在一行中。我们还指定了 d+ 选项以在加入条目时添加“+”字符作为分隔符。 有了这个,我们准备将这个序列作为stdin提供给bc*命令:
$ time echo "Total is: $(cat numbers.csv | paste -sd+ - | bc)"
Total is: 49471228
real 0m0.244s
user 0m0.203s
sys 0m0.063s
值得注意的是,性能优于我们使用 Bash 循环观察到的性能(约 2 秒)。此外,它接近但无法击败awk命令的性能(0.228 秒)。
5.2. 使用tr命令
与*paste *命令类似,让我们再次使用tr 命令生成一个序列:
$ cat numbers.csv | head -10 |tr "\n" "+"
2+44+6+15+23+0+15+88+82+1+
在这里,我们将每个换行符*(’\n’)转换为加号(’+’)字符。但是,请注意序列末尾的额外“+”。作为一种解决方法,我们可以在最后添加一个额外的零来解决这个问题,然后再将它传递给bc*命令:
$ cat numbers.csv | head -10 |tr "\n" "+" ; echo "0"
2+44+6+15+23+0+15+88+82+1+0
现在,让我们将输出重定向到bc命令:
$ time ((cat numbers.csv | tr "\n" "+" ; echo "0") | bc)
49471228
real 0m0.217s
user 0m0.203s
sys 0m0.031s
** tr和bc命令的组合执行速度比awk解决方案快。**
5.3. 使用sed命令
最后,我们将使用sed命令生成序列:
$ cat numbers.csv | head -10 | sed -z 's#\n#+#g'
2+44+6+15+23+0+15+88+82+1+
同样,我们使用sed命令的搜索和替换 选项将换行符*(’\n’)替换为加号(’+’)*字符。此外,我们在末尾打印零来处理额外的加号运算符,类似于上一节:
$ time ((cat numbers.csv | sed -z 's#\n#+#g' ; echo "0") | bc)
49471228
real 0m0.343s
user 0m0.281s
sys 0m0.109s
在这里,使用*-z选项会更改sed命令的换行符的含义。它将不再将\n视为行尾,而是将空字符解释为行尾。实际上,我们可以用加号(’+’)字符替换换行符**(’\n’)*。
请注意,与tr和paste选项相比,用sed替换字符很慢。
在我们结束之前,我们应该知道,对于包含单个数字列的文件,非*awk替代方案可能运行得更快。*但是在许多现实世界的场景中,文件将包含多个列,并且在实际计算发生之前要去除一些附加信息(有点类似于我们在第 3.1 节中的讨论)。
在这种情况下,** awk应该是首选工具,因为非awk替代方案的所有速度优势将被在计算其元素之和之前预处理文件以提取单个列所花费的时间所消耗**。