bash- 查找行中数字的平均值
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/32917920/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
bash- find average of numbers in line
提问by NateDawg87
I am trying to read a file line by line and find the average of the numbers in each line. I am getting the error: expr: non-numeric argument
我正在尝试逐行读取文件并找到每行中数字的平均值。我收到错误:expr: non-numeric argument
I have narrowed the problem down to sum=expr $sum + $i
, but I'm not sure why the code doesn't work.
我已将问题缩小到 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
The file looks like this (the numbers are separated by tabs):
该文件如下所示(数字由制表符分隔):
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
回答by John1024
With some minor corrections, your code runs well:
通过一些小的更正,您的代码运行良好:
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
With the sample input file, the output produced is:
使用示例输入文件,产生的输出是:
1
5
7
5
2
5
Note that the answers are what they are because expr
only does integer arithmetic.
请注意,答案就是它们的样子,因为expr
只进行整数运算。
Using sed to preprocess for expr
使用 sed 预处理 expr
The above code could be rewritten as:
上面的代码可以改写为:
$ while read row; do expr '(' $(sed 's/ */ + /g' <<<"$row") ')' / $(wc -w<<<$row); done < filename
1
5
7
5
2
5
Using bash's builtin arithmetic capability
使用 bash 的内置算术功能
expr
is archaic. In modern 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
Using awk for floating point math
使用 awk 进行浮点数学运算
Because awk does floating point math, it can provide more accurate results:
由于 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
回答by Harvey
Some variations on the same trick of using the IFS variable.
使用 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
回答by ghoti
Others have already pointed out that expr
is integer-only, and recommended writing your script in awk instead of shell.
其他人已经指出这expr
是仅整数,并建议使用 awk 而不是 shell 编写脚本。
Your system may have a number of tools on it that support arbitrary-precision math, or floats. Two common calculators in shell are bc
which follows standard "order of operations", and dc
which uses "reverse polish notation".
您的系统上可能有许多支持任意精度数学或浮点数的工具。shell 中的两种常见计算器bc
遵循标准的“操作顺序”,并dc
使用“反向波兰符号”。
Either one of these can easily be fed your data such that per-line averages can be produced. For example, using bc:
这些中的任何一个都可以轻松地输入您的数据,以便可以生成每行平均值。例如,使用 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
Of course, the only bc
-specific part of this is the format for the notation and the bc
itself in the third last line. The same basic thing using dc
might look like like this:
当然,唯一的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
Note that my shell supports appending to strings with +=
. If yours does not, you can adjust this as you see fit.
请注意,我的 shell 支持使用+=
. 如果没有,您可以根据需要进行调整。
In both of these examples, we're printing our output to four decimal places -- with scale=4
in bc, or 4k
in dc. We are processing standard input, so if you named these scripts "calc", you might run them with command lines like:
在这两个示例中,我们将输出打印到小数点后四位——使用scale=4
in bc 或4k
in dc。我们正在处理标准输入,因此如果您将这些脚本命名为“calc”,您可以使用如下命令行运行它们:
$ ./calc < inputfile.txt
The set
command at the beginning of the loop turns the $line
variable into positional parameters, like $1
, $2
, etc. We then process each positional parameter in the for
loop, appending everything to a string which will later get fed to the calculator.
该set
在循环的开始命令打开了$line
可变进位置参数,比如$1
,$2
等我们再处理在每个位置参数for
环,附加的一切将在后面腻到计算器中的字符串。
Also, you can fake it.
此外,你可以伪造它。
That is, while bash doesn't support floating point numbers, it DOES support multiplication and string manipulation. The following uses NO external tools, yet appearsto present decimal averages of your input.
也就是说,虽然 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
The important bits here are:
* declare
which tells bash to addto $total
with +=
rather than appending it as if it were a string,
* the two average=
assignments, the first of which multiplies $total
by 1000, and the second of which splits the result at the thousands column, and
* printf
whose format enforces three decimal places of precision in its output.
这里的重要部分是: *declare
告诉 bash添加到$total
with+=
而不是像附加字符串一样附加它, * 两个average=
赋值,第一个乘以$total
1000,第二个将结果拆分为数千列, 和 *printf
其格式在其输出中强制执行三个小数位的精度。
Of course, input still needs to be integers.
当然,输入仍然需要是整数。
YMMV. I'm not saying this is how you shouldsolve this, just that it's an option. :)
天啊。我并不是说这是你应该如何解决这个问题,只是说这是一种选择。:)
回答by SamWN
This is a pretty old post, but came up at the top my Google search, so thought I'd share what I came up with:
这是一篇很老的帖子,但在我的谷歌搜索中出现在顶部,所以我想分享一下我的想法:
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"