linux - 多个文件的平均值

标签 linux shell

我正在尝试编写一个 shell 脚本来平均几个格式相同的文件,这些文件的名称为 file1file2file3 等等。

在每个文件中,数据都在一个格式的表格中,例如 4 列和 5 行数据。假设 file1file2file3 在同一目录中。我想要做的是创建一个平均文件,它具有与 file1/file2/file3 相同的格式,它应该有平均表中的每个元素。例如,

{(Element in row 1, column 1 in file1)+
 (Element in row 1, column 1 in file2)+
 (Element in row 1, column 1 in file3)} >> 
(Element in row 1, column 1 in average file)

同样,我需要为表中的每个元素执行此操作,平均文件具有与 file1file2file3 相同数量的元素

我尝试编写一个 shell 脚本,但它不起作用。我想要的是循环读取文件并从每个文件中 grep 相同的元素,将它们相加并根据文件数量进行平均,最后写入类似的文件格式。这是我试图写的:

#!/bin/bash       
s=0
for i in {1..5..1} do
    for j in {1..4..1} do
        for f in m* do
            a=$(awk 'FNR == i {print $j}' $f)
            echo $a
            s=$s+$a
            echo $f
        done
        avg=$s/3
        echo $avg > output
    done
done

最佳答案

这是一种相当低效的处理方式:对于您尝试提取的每一个数字,您都会完全处理一个输入文件——即使您只有三个文件,您也会处理 60 个!

此外,以这种方式混合 Bash 和 awk 是一种巨大的反模式。 This here是解释原因的很好的问答。

补充几点:

  • 对于大括号扩展,默认步长为 1,因此 {1..4..1}{1..4} 相同。
  • Awk 不知道ij 是什么。就它而言,这些从未被定义。如果您真的想将您的 shell 变量放入 awk,您可以这样做

    a=$(awk -v i="$i" -v j="$j" 'FNR == i { print $j }' $f)
    

    但这种方法无论如何都不合理。

  • Shell 算法不像 s=$s+$aavg=$s/3 那样工作——它们只是连接字符串。要让 shell 为您进行计算,您需要进行算术扩展:

    s=$(( s + a ))
    

    或者,更短一点,

    (( s += a ))
    

    avg=$(( s / 3 ))
    

    请注意,在算术上下文中不需要 $ 符号。

  • echo $avg > output 会将每个数字打印在单独的一行上,这可能不是您想要的。
  • 缩进很重要!如果不是为了机器,那就是为了人类读者。

Bash 解决方案

这只使用 Bash 就解决了这个问题。它被硬编码为三个文件,但在行数和每行元素数方面是灵活的。没有检查来确保所有行和文件的元素数量相同。

请注意,Bash 在处理这类事情时快,并且应该只用于小文件,如果有的话。此外,它使用整数运算,因此 3 和 4 的“平均值”将变为 3。

我添加了评论来解释发生了什么。

#!/bin/bash

# Read a line from the first file into array arr1
while read -a arr1; do

    # Read a line from the second file at file descriptor 3 into array arr2
    read -a arr2 <&3

    # Read a line from the third file at file descriptor 4 into array arr3
    read -a arr3 <&4

    # Loop over elements
    for (( i = 0; i < ${#arr1[@]}; ++i )); do

        # Calculate average of element across files, assign to res array
        res[i]=$(( (arr1[i] + arr2[i] + arr3[i]) / 3 ))
    done

    # Print res array
    echo "${res[@]}"

# Read from files supplied as arguments
# Input for the second and third file is redirected to file descriptors 3 and 4
# to enable looping over multiple files concurrently
done < "$1" 3< "$2" 4< "$3"

这必须像这样调用

./bashsolution file1 file2 file3

并且可以根据需要重定向输出。

awk 解决方案

这是纯 awk 中的解决方案。它更灵活一些,因为它取了作为参数提供的许多文件的平均值;它也应该比 Bash 解决方案快大约一个数量级。

#!/usr/bin/awk -f

# Count number of files: increment on the first line of each new file
FNR == 1 { ++nfiles }

{
    # (Pseudo) 2D array summing up fields across files
    for (i = 1; i <= NF; ++i) {
        values[FNR, i] += $i
    }
}

END {
    # Loop over lines of array with sums
    for (i = 1; i <= FNR; ++i) {

        # Loop over fields of current line in array of sums
        for (j = 1; j <= NF; ++j) {

            # Build record with averages
            $j = values[i, j]/nfiles
        }
        print
    }
}

必须这样称呼

./awksolution file1 file2 file3

并且,如前所述,对要平均的文件数量没有限制。

关于linux - 多个文件的平均值,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/37061754/

相关文章:

linux - DFS Used% : 100. 00% 从属虚拟机在 Hadoop 中关闭

c - 当我不想覆盖时我的 shell 实现

linux - 在 sh 中随机选择一个结果

linux - rpmbuild 命令只能构建 1 个子包吗?

mysql - linux下如何将数据库导入mysql?

linux - 在 Linux 上安装 Jenkins,执行 shell 命令给出权限被拒绝。

c - 几乎完成了 linux shell pipe

Linux shell bash 脚本 if 条件

c - 为什么具有自定义函数的 rl_bind_key 在 OSX/macOS 上的 readline 中失败

shell - 如何将\x00 作为参数传递给程序?