bash - 如何在不同字段上连接多个文件(任意数量),并使用默认值填充缺失字段

标签 bash join awk posix

我发布这个问题是因为它是比现有的连接多个文件的用例更通用的用例,并且我在为其编写解决方案时偶然发现了一个小问题。

这是我正在尝试执行的示例(输入文件的数量可能会有所不同):

$ cat f1.tsv
F11 F12 F13
111 A   113
211 B   213

$ cat f2.tsv
F21 F22
121 D

$ cat f3.tsv
F31 F32 F33 F24
131 132 133 C
231 232 233 A
331 332 333 D

$ cat fn.tsv
Fn1 Fn2 Fn3
B   102 103
F   202 203
D   302 303
C   402 403

要加入上述文件,用户将提供 f1.tsv f2.tsv f3.tsv fn.tsv(文件)作为参数和 2 2 4 1 (要连接的字段)作为参数;预期输出为:

F11 F13 F21 F31 F32 F33 Fn2 Fn3 KEY
111 113 0   231 232 233 0   0   A
211 213 0   0   0   0   102 103 B
0   0   0   131 132 133 402 403 C
0   0   121 331 332 333 302 303 D
0   0   0   0   0   0   202 203 F

注意:输入和输出中的字段用单个制表符分隔

这是我到目前为止所写的内容:

awk -v join='2 2 4 1' -v empty='0' '
    BEGIN {
        FS = OFS = "\t"

        if ( err = (ARGC <= 2 || split(join,joinArr,"[[:space:],]") != ARGC-1) )
            exit
    }
    FNR == 1 {
        ++fileNumber

        j = joinArr[fileNumber]+0
        if ( err = (j < 1 || NF < j) )
            exit

        rec = sep = ""
        for (i = 1; i <= NF; i++)
            if (i != j) {
                rec = rec sep $i
                sep = OFS
            }
        header = (fileNumber == 1 ? rec : header OFS rec)
        next
    }
    {
        rec = sep = ""
        for (i = 1; i <= NF; i++)
            if (i != j) {
                rec = rec sep $i
                sep = OFS
            }
        keys[$j]
        records[fileNumber,$j] = rec
    }
    END {
        if (err)
            exit err

        print header, "KEY"

        for (k in keys) {
            rec = sep = ""
            for (i = 1; i <= fileNumber; i++) {
                rec = rec sep ((i,k) in records ? records[i,k] : empty)
                sep = OFS
            }
            rec = rec sep k
            print rec
        }
    }
' f1.tsv f2.tsv f3.tsv fn.tsv

但我明白了:

F11 F13 F21 F31 F32 F33 Fn2 Fn3 KEY
111 113 0   231 232 233 0   A
211 213 0   0   102 103 B
0   0   131 132 133 402 403 C
0   121 331 332 333 302 303 D
0   0   0   202 203 F

备注:我需要它在 Linux 和 macOS 上工作,因此我使用 POSIX awk,但 perl 以及可能的 python(与 2.7 和 3.x 兼容)也可以是一个选项。

最佳答案

一般问题是 empty 被定义为单个字段;应根据每个文件中的字段数量将其定义为可变数量的字段。

调整当前代码以生成一个空[fileNumber]数组:

awk -v join='2 2 4 1' -v empty_str='0' '                                     # modified
    BEGIN {
        FS = OFS = "\t"

        if ( err = (ARGC <= 2 || split(join,joinArr,"[[:space:],]") != ARGC-1) )
            exit
    }
    FNR == 1 {
        ++fileNumber

        j = joinArr[fileNumber]+0
        if ( err = (j < 1 || NF < j) )
            exit
   
        rec = sep = empty_rec = ""                                           # modified
        for (i = 1; i <= NF; i++)
            if (i != j) {
                rec = rec sep $i
                empty_rec = empty_rec sep empty_str                          # new
                sep = OFS
            }
        header = (fileNumber == 1 ? rec : header OFS rec)
        empty[fileNumber] = empty_rec                                        # new
        next
    }
    {
        rec = sep = ""
        for (i = 1; i <= NF; i++)
            if (i != j) {
                rec = rec sep $i
                sep = OFS
            }
        keys[$j]
        records[fileNumber,$j] = rec
    }
    END {
        if (err)
            exit err

        print header, "KEY"

        for (k in keys) {
            rec = sep = ""
            for (i = 1; i <= fileNumber; i++) {
                rec = rec sep ((i,k) in records ? records[i,k] : empty[i])   # modified
                sep = OFS
            }
            rec = rec sep k
            print rec
        }
    }
' f1.tsv f2.tsv f3.tsv fn.tsv

注释:

  • 虽然描述中没有明确说明,但当前(和调整后的)代码假设我们只需处理缺失的行
  • 换句话说:在给定文件中,所有行都将包含相同数量的列(即,我们不必担心某些行缺少一列或多列)

这会生成:

F11     F13     F21     F31     F32     F33     Fn2     Fn3     KEY
111     113     0       231     232     233     0       0       A
211     213     0       0       0       0       102     103     B
0       0       0       131     132     133     402     403     C
0       0       121     331     332     333     302     303     D
0       0       0       0       0       0       202     203     F

关于bash - 如何在不同字段上连接多个文件(任意数量),并使用默认值填充缺失字段,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/76067774/

相关文章:

mysql - 数据丢失时删除行 2 joins away

php - LEFT JOIN 无法正常工作

bash - sed/awk 将成对的行合并为一行

linux - 如何使用 awk 修改文本字段?

linux - 为什么我的 awk 结果与示例不同?

bash - GNU 并行不生成作业

python - 树莓派3开机自动录像

Mysql JOIN 多个表并选择多个值

linux - 使用正则表达式将列与 awk 匹配

git - 在 Cygwin 中无法看到 git 颜色