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

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-18 13:40:26  来源:igfitidea点击:

bash- find average of numbers in line

arraysbashexpr

提问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 expronly 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 的内置算术功能

expris 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 expris 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 bcwhich follows standard "order of operations", and dcwhich 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 bcitself in the third last line. The same basic thing using dcmight 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=4in bc, or 4kin dc. We are processing standard input, so if you named these scripts "calc", you might run them with command lines like:

在这两个示例中,我们将输出打印到小数点后四位——使用scale=4in bc 或4kin dc。我们正在处理标准输入,因此如果您将这些脚本命名为“calc”,您可以使用如下命令行运行它们:

$ ./calc < inputfile.txt

The setcommand at the beginning of the loop turns the $linevariable into positional parameters, like $1, $2, etc. We then process each positional parameter in the forloop, 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: * declarewhich tells bash to addto $totalwith +=rather than appending it as if it were a string, * the two average=assignments, the first of which multiplies $totalby 1000, and the second of which splits the result at the thousands column, and * printfwhose format enforces three decimal places of precision in its output.

这里的重要部分是: *declare告诉 bash添加$totalwith+=而不是像附加字符串一样附加它, * 两个average=赋值,第一个乘以$total1000,第二个将结果拆分为数千列, 和 *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"