【问题标题】:Average of multiple files in shellshell中多个文件的平均值
【发布时间】:2015-07-27 05:43:15
【问题描述】:

我想计算 15 个文件的平均值:- ifile1.txt、ifile2.txt、.....、ifile15.txt。每个文件的列数和行数相同。部分数据看起来像

ifile1.txt      ifile2.txt       ifile3.txt
3  5  2  2 .    1  2  1  3 .    4  3  4  1 .
1  4  2  1 .    1  3  0  2 .    5  3  1  5 .
4  6  5  2 .    2  5  5  1 .    3  4  3  1 .
5  5  7  1 .    0  0  1  1 .    4  3  4  0 .
.  .  .  . .    .  .  .  . .    .  .  .  . .  

我想找到一个新文件,它会显示这 15 个文件的平均值。

ofile.txt
2.66   3.33  2.33 2      . (i.e. average of 3 1 4, average of 5 2 3 and so on)
2.33   3.33  1    2.66   .
3      5     4.33 1.33   .
3      2.33  4    0.66   .
.      .     .    .      .

我正在尝试关注,但出现错误

awk'{for (i=1; i<=NF; i++)} rows=FNR;cols=NF} END 
{for (i=1; i<=rows; i++){for (j=1; j<=cols; j++) 
s+=$i;print $0,s/NF;s=0}}' ifile* > ofile.txt

【问题讨论】:

  • 错误信息可能会帮助别人回答你。
  • 您为什么要访问END 块中的$0$i?在你读完所有文件后运行,没有当前记录要处理。
  • 如 (awk'{for (i=1; i&lt;=NF; i++)} rows=FNR;cols=NF} END …) 所写,您会收到“找不到命令”作为错误,因为您必须在 awk 和脚本 '…' 之间留一个空格。当你修复它时,你开始遇到问题,因为脚本的第一行有两个},只有一个{

标签: shell unix awk mean


【解决方案1】:

如书面:

awk'{for (i=1; i<=NF; i++)} rows=FNR;cols=NF} END
…

您会收到“找不到命令”作为错误,因为您必须在 awk 和引号内的脚本之间留一个空格。当你修复它时,你开始遇到问题,因为脚本的第一行有两个},只有一个{

当您开始解决问题时,您将需要一个二维数组,该数组按行号和列号进行索引,并对文件中的值求和。您还需要知道处理的文件数和列数。然后,您可以安排在 END 块中迭代二维数组。

awk 'FNR == 1 { nfiles++; ncols = NF }
     { for (i = 1; i < NF; i++) sum[FNR,i] += $i
       if (FNR > maxnr) maxnr = FNR
     }
     END {
         for (line = 1; line <= maxnr; line++)
         {
             for (col = 1; col < ncols; col++)
                  printf "  %f", sum[line,col]/nfiles;
             printf "\n"
         }
     }' ifile*.txt

鉴于问题中的三个数据文件:

ifile1.txt

3 5 2 2
1 4 2 1
4 6 5 2
5 5 7 1

ifile2.txt

1 2 1 3
1 3 0 2
2 5 5 1
0 0 1 1

ifile3.txt

4 3 4 1
5 3 1 5
3 4 3 1
4 3 4 0

我展示的脚本产生:

  2.666667  3.333333  2.333333
  2.333333  3.333333  1.000000
  3.000000  5.000000  4.333333
  3.000000  2.666667  4.000000

如果要将小数位数控制为 2,则使用 %.2f 代替 %f

【讨论】:

  • 我不知道它是否只是 GNU awk 的东西(我使用),但 FNRNF 的值在 END{ } 块中仍然有效 - 所以这将节省单独处理这些变量的需要(至少在GNU awk 中)。 +1
  • 它不应该只是 GNU awk 的一个特性;最后一个文件的最后一行的信息也可能在 POSIX 标准awk 中可用。也就是说,我可能会加强代码以发现不正确的输入文件——太多或太少的列,或者太多或太少的行。我根本不相信用户,包括我自己,不会滥用系统。 GIGO 很好,但发现垃圾进入可以避免问题。
  • 检查awk的POSIX规范;它明确表示 FNR 和 NF 都保留 END 块中最后一个文件的最后一行的值。
  • 为什么结果缺少一列?如何解决此问题以显示所有列?
  • @And — 缺少的列是因为我使用了i &lt; NF 而不是i &lt;= NF。修复就是这么复杂。
【解决方案2】:
$ { head -n1 ifile1.txt; paste ifile*.txt;} | awk 'NR==1{d=NF; next;} {for (i=1;i<=d;i++) {s=0; for (j=i;j<=NF;j+=d) s+=$j; printf "%.2f%s",s/(NF/d),j==NF+d?"\n":"\t";}}'
2.67    3.33    2.33    2.00
2.33    3.33    1.00    2.67
3.00    5.00    4.33    1.33
3.00    2.67    4.00    0.67

此脚本计算每一行并在继续下一行之前打印结果。因此,脚本不需要一次将所有数据保存在内存中。如果数据文件很大,这一点很重要。

工作原理

  • { head -n1 ifile1.txt; paste ifile*.txt;}

    这只会打印ifile1.txt 的第一行。然后,paste 命令使其打印所有合并文件的第一行,然后合并第二行,以此类推:

    $ paste ifile*.txt
    3  5  2  2      1  2  1  3      4  3  4  1
    1  4  2  1      1  3  0  2      5  3  1  5
    4  6  5  2      2  5  5  1      3  4  3  1
    5  5  7  1      0  0  1  1      4  3  4  0
    
  • |

    管道符号导致上述命令的输出作为输入发送到 awk。依次处理每个 awk 命令:

  • NR==1{d=NF; next;}

    对于第一行,我们将列数保存在变量d 中。然后,我们跳过其余的命令并从 next 输入行重新开始。

  • for (i=1;i&lt;=d;i++) {s=0; for (j=i;j&lt;=NF;j+=d) s+=$j; printf "%.2f%s",s/(NF/d),j==NF+d?"\n":"\t";}

    这会将各个文件中的数字相加并打印平均值。

作为多行脚本:

{
    head -n1 ifile1.txt
    paste ifile*.txt
} | 
awk '
    NR==1 {d=NF; next;}

    {
        for (i=1;i<=d;i++)
        {
            s=0; for (j=i;j<=NF;j+=d)
                s+=$j;
            printf "%.2f%s",s/(NF/d),j==NF+d?"\n":"\t";
        }
    }

【讨论】:

    【解决方案3】:

    在读取原始文件时,您需要将字段总和保存到一个数组中。您无法访问END 块中的$0i,因为那时没有输入行。

    awk '{rows=FNR; cols=NF; for (i = 1; i <= NF; i++) { total[FNR, i] += $i }}
         FILENAME != lastfn { count++; lastfn = FILENAME }
         END { for (i = 1; i <= rows; i++) { 
                    for (j =  1; j <= cols; j++) {
                        printf("%s ", total[i, j]/count)
                    }
                    printf("\n")
                }
            }' ifile* > ofile.txt
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2013-07-19
      • 2015-06-30
      • 2017-02-06
      • 1970-01-01
      • 2019-08-01
      • 1970-01-01
      • 2017-08-13
      相关资源
      最近更新 更多