bash- 求行中数字的平均值
bash- find average of numbers in line
我正在尝试逐行读取文件并计算每行中数字的平均值。我收到错误:expr: non-numeric argument
我已将问题缩小到 sum=expr $sum + $i
,但我不确定为什么代码不起作用。
while read -a rows
do
for i in "${rows[@]}"
do
sum=`expr $sum + $i`
total=`expr $total + 1`
done
average=`expr $sum / $total`
done < $fileName
文件如下所示(数字由制表符分隔):
1 1 1 1 1
9 3 4 5 5
6 7 8 9 7
3 6 8 9 1
3 4 2 1 4
6 4 4 7 7
经过一些小的更正,您的代码运行良好:
while read -a rows
do
total=0
sum=0
for i in "${rows[@]}"
do
sum=`expr $sum + $i`
total=`expr $total + 1`
done
average=`expr $sum / $total`
echo $average
done <filename
使用样本输入文件,产生的输出是:
1
5
7
5
2
5
请注意,答案就是它们的原样,因为 expr
只会进行整数运算。
使用 sed 对 expr 进行预处理
以上代码可以改写为:
$ while read row; do expr '(' $(sed 's/ */ + /g' <<<"$row") ')' / $(wc -w<<<$row); done < filename
1
5
7
5
2
5
使用bash的内置算术能力
expr
是过时的。在现代 bash:
while read -a rows
do
total=0
sum=0
for i in "${rows[@]}"
do
((sum += $i))
((total++))
done
echo $((sum/total))
done <filename
使用 awk 进行浮点运算
因为 awk 进行浮点运算,它可以提供更准确的结果:
$ awk '{s=0; for (i=1;i<=NF;i++)s+=$i; print s/NF;}' filename
1
5.2
7.4
5.4
2.8
5.6
使用 IFS 变量的相同技巧的一些变体。
#!/bin/bash
while read line; do
set -- $line
echo $(( ( $(IFS=+; echo "$*") ) / $# ))
done < rows
echo
while read -a line; do
echo $(( ( $(IFS=+; echo "${line[*]}") ) / ${#line[*]} ))
done < rows
echo
saved_ifs="$IFS"
while read -a line; do
IFS=+
echo $(( ( ${line[*]} ) / ${#line[*]} ))
IFS="$saved_ifs"
done < rows
其他人已经指出 expr
仅是整数,建议用 awk 编写脚本而不是 shell。
您的系统上可能有许多支持任意精度数学或浮点数的工具。 shell 中的两个常见计算器是遵循标准 "order of operations" 的 bc
和使用 "reverse polish notation".
的 dc
您可以轻松地将其中任何一个输入您的数据,以便生成每行平均值。例如,使用 bc:
#!/bin/sh
while read line; do
set - ${line}
c=$#
string=""
for n in $*; do
string+="${string:++}"
shift
done
average=$(printf 'scale=4\n(%s) / %d\n' $string $c | bc)
printf "%s // avg=%s\n" "$line" "$average"
done
当然,其中唯一 bc
特定的部分是符号的格式和倒数第三行中的 bc
本身。使用 dc
的相同基本内容可能如下所示:
#!/bin/sh
while read line; do
set - ${line}
c=$#
string="0"
for n in $*; do
string+=" + "
shift
done
average=$(dc -e "4k $string $c / p")
printf "%s // %s\n" "$line" "$average"
done
请注意,我的 shell 支持使用 +=
附加到字符串。如果您的没有,您可以根据需要进行调整。
在这两个示例中,我们将输出打印到小数点后四位——bc 为 scale=4
,dc 为 4k
。我们正在处理标准输入,因此如果您将这些脚本命名为 "calc",您可能 运行 它们的命令行如下:
$ ./calc < inputfile.txt
循环开始的set
命令将$line
变量转换为位置参数,如</code>、<code>
等。然后我们处理每个位置参数for
循环中的参数,将所有内容附加到一个字符串,该字符串稍后将被输入计算器。
还有,你可以伪装一下
也就是说,虽然 bash 不支持浮点数,但它支持乘法和字符串操作。以下内容不使用任何外部工具,但 显示 以显示您输入的十进制平均值。
#!/bin/bash
declare -i total
while read line; do
set - ${line}
c=$#
total=0
for n in $*; do
total+=""
shift
done
# Move the decimal point over prior to our division...
average=$(($total * 1000 / $c))
# Re-insert the decimal point via string manipulation
average="${average:0:$((${#average} - 3))}.${average:$((${#average} - 3))}"
printf "%s // %0.3f\n" "$line" "$average"
done
这里的重要部分是:
* declare
告诉 bash 到 添加 到 $total
和 +=
而不是像字符串一样附加它,
* 两个 average=
赋值,第一个将 $total
乘以 1000,第二个将结果拆分到千位列,以及
* printf
其格式在其输出中强制执行小数点后三位精度。
当然,输入还是需要整数。
YMMV。我并不是说您 应该 解决这个问题,只是说这是一个选项。 :)
这是一个相当古老的 post,但在我的 Google 搜索中排在首位,所以我想分享一下我的发现:
while read line; do
# Convert each line to an array
ARR=( $line )
# Append each value in the array with a '+' and calculate the sum
# (this causes the last value to have a trailing '+', so it is added to '0')
ARR_SUM=$( echo "${ARR[@]/%/+} 0" | bc -l)
# Divide the sum by the total number of elements in the array
echo "$(( ${ARR_SUM} / ${#ARR[@]} ))"
done < "$filename"
我正在尝试逐行读取文件并计算每行中数字的平均值。我收到错误:expr: non-numeric argument
我已将问题缩小到 sum=expr $sum + $i
,但我不确定为什么代码不起作用。
while read -a rows
do
for i in "${rows[@]}"
do
sum=`expr $sum + $i`
total=`expr $total + 1`
done
average=`expr $sum / $total`
done < $fileName
文件如下所示(数字由制表符分隔):
1 1 1 1 1
9 3 4 5 5
6 7 8 9 7
3 6 8 9 1
3 4 2 1 4
6 4 4 7 7
经过一些小的更正,您的代码运行良好:
while read -a rows
do
total=0
sum=0
for i in "${rows[@]}"
do
sum=`expr $sum + $i`
total=`expr $total + 1`
done
average=`expr $sum / $total`
echo $average
done <filename
使用样本输入文件,产生的输出是:
1
5
7
5
2
5
请注意,答案就是它们的原样,因为 expr
只会进行整数运算。
使用 sed 对 expr 进行预处理
以上代码可以改写为:
$ while read row; do expr '(' $(sed 's/ */ + /g' <<<"$row") ')' / $(wc -w<<<$row); done < filename
1
5
7
5
2
5
使用bash的内置算术能力
expr
是过时的。在现代 bash:
while read -a rows
do
total=0
sum=0
for i in "${rows[@]}"
do
((sum += $i))
((total++))
done
echo $((sum/total))
done <filename
使用 awk 进行浮点运算
因为 awk 进行浮点运算,它可以提供更准确的结果:
$ awk '{s=0; for (i=1;i<=NF;i++)s+=$i; print s/NF;}' filename
1
5.2
7.4
5.4
2.8
5.6
使用 IFS 变量的相同技巧的一些变体。
#!/bin/bash
while read line; do
set -- $line
echo $(( ( $(IFS=+; echo "$*") ) / $# ))
done < rows
echo
while read -a line; do
echo $(( ( $(IFS=+; echo "${line[*]}") ) / ${#line[*]} ))
done < rows
echo
saved_ifs="$IFS"
while read -a line; do
IFS=+
echo $(( ( ${line[*]} ) / ${#line[*]} ))
IFS="$saved_ifs"
done < rows
其他人已经指出 expr
仅是整数,建议用 awk 编写脚本而不是 shell。
您的系统上可能有许多支持任意精度数学或浮点数的工具。 shell 中的两个常见计算器是遵循标准 "order of operations" 的 bc
和使用 "reverse polish notation".
dc
您可以轻松地将其中任何一个输入您的数据,以便生成每行平均值。例如,使用 bc:
#!/bin/sh
while read line; do
set - ${line}
c=$#
string=""
for n in $*; do
string+="${string:++}"
shift
done
average=$(printf 'scale=4\n(%s) / %d\n' $string $c | bc)
printf "%s // avg=%s\n" "$line" "$average"
done
当然,其中唯一 bc
特定的部分是符号的格式和倒数第三行中的 bc
本身。使用 dc
的相同基本内容可能如下所示:
#!/bin/sh
while read line; do
set - ${line}
c=$#
string="0"
for n in $*; do
string+=" + "
shift
done
average=$(dc -e "4k $string $c / p")
printf "%s // %s\n" "$line" "$average"
done
请注意,我的 shell 支持使用 +=
附加到字符串。如果您的没有,您可以根据需要进行调整。
在这两个示例中,我们将输出打印到小数点后四位——bc 为 scale=4
,dc 为 4k
。我们正在处理标准输入,因此如果您将这些脚本命名为 "calc",您可能 运行 它们的命令行如下:
$ ./calc < inputfile.txt
循环开始的set
命令将$line
变量转换为位置参数,如</code>、<code>
等。然后我们处理每个位置参数for
循环中的参数,将所有内容附加到一个字符串,该字符串稍后将被输入计算器。
还有,你可以伪装一下
也就是说,虽然 bash 不支持浮点数,但它支持乘法和字符串操作。以下内容不使用任何外部工具,但 显示 以显示您输入的十进制平均值。
#!/bin/bash
declare -i total
while read line; do
set - ${line}
c=$#
total=0
for n in $*; do
total+=""
shift
done
# Move the decimal point over prior to our division...
average=$(($total * 1000 / $c))
# Re-insert the decimal point via string manipulation
average="${average:0:$((${#average} - 3))}.${average:$((${#average} - 3))}"
printf "%s // %0.3f\n" "$line" "$average"
done
这里的重要部分是:
* declare
告诉 bash 到 添加 到 $total
和 +=
而不是像字符串一样附加它,
* 两个 average=
赋值,第一个将 $total
乘以 1000,第二个将结果拆分到千位列,以及
* printf
其格式在其输出中强制执行小数点后三位精度。
当然,输入还是需要整数。
YMMV。我并不是说您 应该 解决这个问题,只是说这是一个选项。 :)
这是一个相当古老的 post,但在我的 Google 搜索中排在首位,所以我想分享一下我的发现:
while read line; do
# Convert each line to an array
ARR=( $line )
# Append each value in the array with a '+' and calculate the sum
# (this causes the last value to have a trailing '+', so it is added to '0')
ARR_SUM=$( echo "${ARR[@]/%/+} 0" | bc -l)
# Divide the sum by the total number of elements in the array
echo "$(( ${ARR_SUM} / ${#ARR[@]} ))"
done < "$filename"