【问题标题】:How to split files according first column awk如何根据第一列 awk 拆分文件
【发布时间】:2016-11-22 14:12:12
【问题描述】:

我举例说明我需要做什么:

输入:

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

我需要保留标题并根据第一列中的 same 名称拆分为文件。 我需要根据第一列继续命名我的输出文件。

输出:

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

【问题讨论】:

  • 它们是否已排序?例如:所有john 条目总是彼此相邻?
  • 是的,总是按名称排序。
  • 最好编辑带有排序要求的问题,并更改样本输入和预期输出以反映排序

标签: bash unix awk sed


【解决方案1】:

使用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,保存在header中以备后用。

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

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


编辑

如果你想对第二列的文件进行排序,那么我们可以先排序,然后通过管道将它们传递给awklike,

$ sort -n -k2 file | awk ....
  • -n 数字排序。
  • -k2 按第二个键排序。

如果标题也是数字,这可能不起作用

【讨论】:

  • 建议 awk 'NR == 1{header = $0; next} !($1 in filename){ print header &gt; $1".tsv" } { print $0 &gt; $1".tsv"; filename[$1] }' 避免为标题创建文件并匹配 OP 预期的文件扩展名
  • 你复制了我还没有发布的答案!
  • @JamesBrown 是的,如果标题也是数字。
  • @EdMorton 对不起,我没有得到关于未加说明的表达的第一点。关于结束,这对我来说是新的,我想我不需要编辑我的答案,因为你已经添加了一个更好的解决方案。谢谢。
  • @EdMorton 哦,好的。我已经更正了我的答案。谢谢你的解释。
【解决方案2】:

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

在保留标题的同时按前 2 列对输入文件进行排序:

$ 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

【讨论】:

  • Ed 谢谢你的好解决方案。是否可以像终端中的一个班轮一样使用您的 awk 代码?我尝试使用: awk 'NR==1 {hdr=$0; next }$1 != prev {close(out) out=$1 ".csv" print hdr > out prev = $1} {print>out}' infile。但它不起作用。
  • 当然,只需用; 替换每个换行符,{ 之后的换行符除外。
  • @EdMorton Ed 你对 awk 的了解令人惊叹。感谢您分享您的经验。
【解决方案3】:

类似于@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

【讨论】:

  • 感谢您的帮助。我有这个错误信息:NR==1{h=$0;下一个}!p[$1]++ {打印 h > $1}{打印 | “sort -k2 >>” >>> $
  • 你的脚本是否像我发布的那样用单引号括起来?
  • 无括号的输出重定向是每个 POSIX 未定义的行为,因此这可能是导致 OPs 语法错误的原因。 $1 可能包含空格,因此您需要引用它。切换输出文件时可能需要 close() 管道。
猜你喜欢
  • 2013-05-14
  • 1970-01-01
  • 1970-01-01
  • 2019-06-09
  • 2014-12-31
  • 2020-01-30
  • 1970-01-01
  • 2021-06-25
  • 1970-01-01
相关资源
最近更新 更多