如何根据第一列awk分割文件

时间:2016-11-22 14:12:12

标签: bash unix awk sed

我举例说明了我需要做的事情:

INPUT:

name value1 value2 value3
john xxxxx yyyyy qqqqqq
john xxxxx ddddd vvvvvv
john mmmmm jjjjj llllll
paul xxxxx yyyyy qqqqqq
paul ccccc ccccc dddddd

我需要在第一列中根据相同的名称保留标题并拆分成文件。 我需要根据第一栏继续命名我的输出文件。

输出:

FILE1 :john.tsv

name value1 value2 value3
john xxxxx yyyyy qqqqqq
john xxxxx ddddd vvvvvv
john mmmmm jjjjj llllll

FILE2 :paul.tsv

name value1 value2 value3
paul xxxxx yyyyy qqqqqq
paul ccccc ccccc dddddd

INPUT和OUTPUT文件是制表符分开的。标题总是一样的。

我的解决方案非常复杂且缓慢:

head -1 INPUT > header

awk 'NR>1{print $1}' | sort | uniq > names

while read line

 do grep $line INPUT | cat header - > $line.tsv

< names

done

3 个答案:

答案 0 :(得分:5)

使用awk,我们可以编写类似的内容,

$ awk 'NR == 1{header = $0; next} 
    !($1 in filename){ print header > ($1".tsv") } 
    NR > 1 { print $0 > ($1".tsv"); filename[$1] }' file

它的作用是什么?

  • NR == 1{header = $0}如果读取的记录数为1,则这是标题,将其保存在header中供以后使用。

  • NR > 1 { print $0 > ($1".tsv"); filename[$1] }如果我们已经阅读了多条记录,请将该行的内容打印到文件名$1,即第一列。

    • filename[$1]我们将文件名保存在由文件名索引的关联数组中。该数组用于打印标题。
  • ($1 in filename){ print header > ($1".tsv") }如果我们在filename数组中找不到当前文件名,那么它就是第一次出现。所以我们将标题打印到文件中。

修改

如果要对第二列上的文件进行排序,那么我们可以先对它们进行排序,然后将它们传递给awk,例如,

$ sort -n -k2 file | awk ....
  • -n数字排序。
  • -k2排序第二把钥匙。

如果标题也是数字,则可能无效

答案 1 :(得分:3)

到目前为止发布的所有答案都存在使它们易碎和/或不可移植的问题(例如,使用getline而不检查结果,输出重定向的右侧未使用,使用特定于gawk的功能,而不是关闭完成后的每个输出文件)和/或不必要的复杂。

要按前两列对输入文件进行排序,同时保留标题为:

$ awk -v OFS='\t' '{print (NR>1), $0}' file | sort | cut -f2-
name value1 value2 value3
john mmmmm jjjjj llllll
john xxxxx ddddd vvvvvv
john xxxxx yyyyy qqqqqq
paul ccccc ccccc dddddd
paul xxxxx yyyyy qqqqqq

并且可靠,便携,高效地打印输入,包括标题行以分隔基于第一列命名的文件:

$ cat tst.awk
NR==1 { hdr=$0; next }
$1 != prev {
    close(out)
    out = $1 ".tsv"
    print hdr > out
    prev = $1
}
{ print > out }

所以把它们放在一起就是:

awk -v OFS='\t' '{print (NR>1), $0}' file | sort | cut -f2- | awk -f tst.awk

答案 2 :(得分:2)

类似于@ nu11p01n73R的回答,在脚本中添加了数据部分的排序

$ awk 'NR==1{h=$0; next}
    !p[$1]++{print h > $1} 
            {print | "sort -k2 >> " $1}' file

$ head paul john

==> paul <==
name value1 value2 value3
paul ccccc ccccc dddddd
paul xxxxx yyyyy qqqqqq

==> john <==
name value1 value2 value3
john mmmmm jjjjj llllll
john xxxxx ddddd vvvvvv
john xxxxx yyyyy qqqqqq