从多个文件创建一个表

从多个文件创建一个表

我想合并多个两列制表符分隔的文件,格式如下:

  a
A 5
C 4  
D 2

  b
A 2
B 5
C 3

  c
B 4
C 4
D 2

放入以下格式的单个表中:

  a b c
A 5 2 0
B 0 5 4
C 4 3 4
D 2 0 2

答案1

join是可以使用的工具,但它的选项有点令人讨厌:

join -t $'\t' -a1 -a2 -o 0,1.2,2.2     file1 file2 |
join -t $'\t' -a1 -a2 -o 0,1.2,1.3,2.2     - file3 |
sed 's/\t\(\t\|$\)/\t0\1/g'
    a   b   c
A   5   2   0
B   0   5   4
C   4   3   4
D   2   0   2

我首先使用了该-e选项,但这导致了标题行出现问题。

答案2

您本质上是想创建一个二维值数组。每行的第一列对应于钥匙,取自每个输入文件中每行的第一个制表符分隔字段。以下每一列对应一个单独的输入文件。

awk 'BEGIN {
         RS = "(\r\n|\n\r|\r|\n)"
         FS = " *\t *"
         SUBSEP = ":"
     }
     FNR==1 {
         ++file
     }
     NF>=2 {
         if ($1 in keynum)
             key = keynum[$1]
         else {
             key = ++keys
             keynum[$1] = key
             keystr[key] = $1
         }
         value[key,file] = $2
     }
     END {
         files = file
         for (key = 1; key <= keys; key++) {
             printf "%s", keystr[key]
             for (file = 1; file <= files; file++)
                 printf "\t%s", value[key,file]
             printf "\n"
         }
     }' INPUT1 INPUT2 ... INPUTN

BEGIN规则将记录分隔符设置为任何类型的换行符,以便每一行都是一个单独的记录。它还将字段分隔符设置为制表符,包括其周围的任何空格。

在 awk 中,所有数组都是关联的,并且基本上是一维的。通过连接索引(SUBSEP中间有一个)来支持多维数组。这里,我们使用:作为分隔符,因为使用的索引是正整数。 (如果您愿意,您可以使用许多其他字符;例如 tab \t。)

FNR==1规则在每个输入文件的第一行触发。我们增加变量file,使其1适用于第一个输入文件、2第二个输入文件,依此类推。

NF>=2对于至少具有两个字段的所有记录都会触发该规则。在本例中,这意味着每行都有一个制表符。第一个字段是钥匙,第二个字段价值

该变量key是一个正整数,指的是唯一的键字符串。 (1 指第一个唯一的钥匙在所有输入文件中看到,2 到第二个,依此类推。)

关联数组keynum将键字符串映射到键数字(key,正整数)。这keystr是逆映射,将键数字映射到键字符串。

NF>=2规则中,如果第一个字段已经是已知键,则查找其编号。否则,第一个字段将作为新的唯一键字符串添加。然后,第二个字段被保存到value数组中。

END处理完所有输入文件后将触发该规则。该value数组是一个逻辑上的二维数组,包含我们想要的字段。

外循环key按照第一次看到的顺序循环遍历所有看到的唯一键。外循环的每次迭代都会产生一个输出行。

内部循环file按照输入文件的列出顺序循环遍历每个输入文件。每次迭代都会在当前行输出中生成一个附加列。每个输出行包含的列比指定的输入文件的数量正好多一列。 (请注意,如果未指定输入文件,awk 将从标准输入读取,并且将其视为一个输入文件。)

这绝对不是实现这一目标的最简单方法,但我喜欢这个,因为它很强大(接受在 Unix、Linux、旧 Mac、新 Mac、Windows 中创建的输入文件——基本上在任何使用 ASCII 兼容字符集的地方;此外,如果某些输入文件只有所有已知键的子集,也不会感到困惑),相对容易理解、维护和适应类似的情况。


如果您想将上述内容作为脚本运行,请将以下内容另存为例如paste.awk

#!/usr/bin/awk -f
BEGIN {
    RS = "(\r\n|\n\r|\r|\n)"
    FS = " *\t *"
    SUBSEP = ":"
}
FNR==1 {
    ++file
}
NF>=2 {
    if ($1 in keynum)
        key = keynum[$1]
    else {
        key = ++keys
        keynum[$1] = key
        keystr[key] = $1
    }
    printf "key = %s, file = %s, value = %s\n", key, file, $2 >/dev/stderr
    value[key,file] = $2
}
END {
    files = file
    for (key = 1; key <= keys; key++) {
        printf "%s", keystr[key]
        for (file = 1; file <= files; file++)
            printf "\t%s", value[key,file]
        printf "\n"
    }
}

如果你有input1包含

        a
A       5
C       4
D       2

input2含有

        b
A       2
B       5
C       3

input3含有

        c
B       4
C       4
D       2

但每行的第二个字符是Tab;即使用例如创建

printf ' \ta\nA\t5\nC\t4\nD\t2\n' > input1
printf ' \tb\nA\t2\nB\t5\nC\t3\n' > input2
printf ' \tc\nB\t4\nC\t4\nD\t2\n' > input3

或者,如果您将上面的文本复制并粘贴到文件中,请运行sed -e 's|^\(.\) *|\1\t|' -i input1 input2 input3以修复它们;然后,运行

paste.awk input1 input2 input3

输出

        a       b       c
A       5       2       
C       4       3       4
D       2               2
B               5       4

只不过上面的连续空格实际上是tabs。您会看到,该网站上的软件将制表符转换为空格。

编辑添加:如果您想对缺失的条目使用某些预定义值,请将END规则修改为

END {
    files = file
    for (key = 1; key <= keys; key++) {
        printf "%s", keystr[key]
        for (file = 1; file <= files; file++)
            if ((key SUBSEP file) in value)
                printf "\t%s", value[key,file]
            else
                printf "\t%s", blank
        printf "\n"
    }
}

并设置变量blank以反映您想要的值。 (您可以使用 命令行设置它,或者修改 awk 代码并在规则中的某处或规则的开头./paste.awk -v blank=0 input1 input2 input3设置值。)BEGINEND

答案3

这是 GNU awk 版本。首先,我找到所有键值,因此我可以用零填充空值:

keys=$(cut -d $'\t' -f1 file{1,2,3} | sort -u | paste -sd,)
gawk -F'\t' -v keys="$keys" '
    BEGIN {
        n = split(keys,k,/,/)
        for (i=1; i<=n; i++) values[k[i]] = k[i]
    }
    {v[$1] = $2} 
    ENDFILE {
        for (key in values) 
            values[key] = values[key] FS (v[key] ? v[key] : 0)
        delete v
    } 
    END {
        for (key in values) print values[key]
    }
' file1 file2 file3 | sort -t $'\t' -k 1,1

相关内容