如何合并制表符分隔的文件?

如何合并制表符分隔的文件?

我有 3 个制表符分隔的文件,如下所示:

文件 1:

1   Hhe.7
2   Hpyl.1
10  Hac.2

文件 2:

3   Hac.2
15  Hpyl.1
33  Hhe.7

文件 3:

70  Hpyl.1
23  Hhe.7
9   Hac.2

如何将这些文件(使用命令行)合并到一个文件中以获得以下输出:

1  33  23  Hhe.7
2  15  70  Hpyl.1
10  3  9  Hac.2

答案1

用于此目的的经典 UNIX 工具是join

NAME
       join - join lines of two files on a common field

SYNOPSIS
       join [OPTION]... FILE1 FILE2

DESCRIPTION
       For  each  pair of input lines with identical join fields, write a line
       to standard output.  The default join field is the first, delimited  by
       blanks.

但是,joini) 需要对输入进行排序才能工作,并且 ii) 只能处理 2 个文件。因此,您可以做一些丑陋而不优雅的事情,例如:

  1. 按第二个字段对每个文件进行排序并保存为新文件

    sort -k2 file1 > sorted1
    sort -k2 file2 > sorted2
    sort -k2 file3 > sorted3
    
  2. 将文件 1 和文件 2 合并为一个新文件,然后合并第三个文件

    $ join -j2 --nocheck-order sorted1 sorted2 > newfile
    $ join -o 1.2,1.3,2.1,1.1  -1 1 -2 2 --nocheck-order newfile sorted3 
    10 3 9 Hac.2
    1 33 23 Hhe.7
    2 15 70 Hpyl.1
    

    使用的选项是:

       -1 FIELD
              join on this FIELD of file 1
    
       -2 FIELD
              join on this FIELD of file 2
       -j FIELD
              equivalent to '-1 FIELD -2 FIELD'
    
       --nocheck-order
              do not check that the input is correctly sorted
    
       -o FORMAT
              obey FORMAT while constructing output line
       FORMAT is one or more  comma  or  blank  separated
       specifications, each being 'FILENUM.FIELD' or '0'. 
    

    因此,该命令将连接第一个文件的第 1 个字段和第二个文件的第二个字段,并将打印第一个文件的第二个字段(1.2),然后打印第一个文件的第三个字段(1.3),第二个文件的第一个字段(2.1)和第一个文件的第 1 个字段(1.1)

或者,你可以将整个过程组合成一个非常复杂的命令:

$ join -o 1.1,2.2,2.3,2.1 -1 2 -2 1  --nocheck-order <(sort -k2 file3) \
      <(join -j2  --nocheck-order <(sort -k2 file1) <(sort -k2 file2)) 
9 10 3 Hac.2
23 1 33 Hhe.7
70 2 15 Hpyl.1

如果你不喜欢神秘的命令行,你可以使用一个小脚本:

$ awk '{a[$NF]=$1"\t"a[$NF];} END{for(i in a){print a[i],i}}' file{1,2,3} 
23  33  1    Hhe.7
9   3   10   Hac.2
70  15  2    Hpyl.1

答案2

这是的一项工作join,它可以连接两个文件的公共字段:

$ join -11 -22 -o1.2,1.3,2.1,0 <(join -j2 <(sort -k2,2 f1.txt) <(sort -k2,2 f2.txt)) <(sort -k2,2 f3.txt)
10 3 9 Hac.2
1 33 23 Hhe.7
2 15 70 Hpyl.1

由于join每次仅接受两个输入文件,我们使用过程替换( )将前两个文件的输出与第三个文件<()的输出传递。join

答案3

在一个小的 Python 脚本中,你可以组合一个无限文件数量:

#!/usr/bin/env python3
import sys

#read the files, split the lines for reordering
lines = sum([[l.strip().split() for l in open(f).readlines()]\
             for f in sys.argv[1:]], [])
# get the unique last sections
values = set(map(lambda x:x[1], lines))
# combine them with the combined first sections
newlist = [[y[0] for y in lines if y[1]==x]+[x] for x in values]
for l in newlist:
    print(("\t").join(l))

将其复制到一个空文件中,另存为merge.py,通过命令运行:

python3 /path/to/merge.py file1, file2, file3 (file4, file5 etc.)

示例文件上的输出:

10  3   9   Hac.2
1   33  23  Hhe.7
2   15  70  Hpyl.1

添加更多文件

如上所述,如果我添加第 4 个文件,文件数量原则上是无限的:

40   Hhe.7
50   Hpyl.1
60   Hac.2

并运行命令:

python3 /path/to/merge.py file1, file2, file3, file4

输出将是:

40  23  33  1   Hhe.7
50  70  15  2   Hpyl.1
60  9   3   10  Hac.2

答案4

回答者:

以下脚本应该对所有作为参数传递的制表符分隔文件的第 1 列(字段)进行外连接。它使用 加入 命令,对已排序的文件进行外连接,每次 2 个文件。

它将连接文件中的每一行,包括标题行。如果您希望排除标题,请将这两个sort命令更改为生成忽略标题的排序文件的内容。

#!/bin/sh
if test $# -lt 2
then
    echo usage: gjoin file1 file2 ...
    exit 1
fi
sort -t $'\t' -k 1 "$1" > result
shift
for f in "$@"
do
    sort -t $'\t' -k 1 "$f" > temp
    join -1 1 -2 1 -t $'\t' result temp > newresult
    mv newresult result
done
cat result
rm result temp

如果您使用的是旧版 shell,$'\t'则不会被制表符替换,因此您需要使用 'TAB',在引号之间放置文字制表符。

如果您可以使用现代 shell(例如 bash 或 ksh),则可以进行优化/bin/sh;例如,以下几行

sort -t $'\t' -k 1 "$f" > temp
join -1 1 -2 1 -t $'\t' result temp > newresult

可以替换为

join -1 1 -2 1 -t $'\t' result <(sort -t $'\t' -k 1 "$f") > newresult

相关内容