awk:按列名拆分文件并向每个文件添加标题行

awk:按列名拆分文件并向每个文件添加标题行

我有一个管道分隔文件a.txt,其中包含标题行。第一列包含文件名。

我想分成a.txt几个不同的文件 - 其名称由第一列确定。我还希望在a.txt每个文件的顶部重复标题行。

所以我有a.txt

filename|count|age
1.txt|1|15
1.txt|2|14
2.txt|3|1
41.txt|44|1
2.txt|1|3

我想创造1.txt

filename|count|age
1.txt|1|15
1.txt|2|14

2.txt

filename|count|age
2.txt|3|1
2.txt|1|3

41.txt

filename|count|age
41.txt|44|1

我有基本的分工工作

awk -F\| '{print>$1}' a.txt

但我正在努力弄清楚如何包含标题,有人可以帮忙吗?谢谢!

答案1

解决方案是将标头存储在单独的变量中,并在第一次出现新$1值(=文件名)时打印它:

awk -F'|' 'FNR==1{hdr=$0;next} {if (!seen[$1]++) print hdr>$1; print>$1}' a.txt 
  • 这会将整个第一行存储a.txt在变量中hdr,但否则会保留该特定行未处理。
  • 在所有后续行中,我们首先通过在保存各种值的出现计数的$1数组中查找该值(=所需的输出文件名)来检查是否已经遇到。如果当前值的计数器仍然为零,则将标头输出到 指示的文件,然后增加计数器以抑制所有以后发生的标头输出。剩下的你自己已经弄清楚了。seen$1$1$1

附录:

如果您有多个输入文件,并且所有文件都有一个标题行,您可以简单地将它们全部作为调用的参数awk,如下所示

awk -F'|' ' ... ' a.txt b.txt c.txt ...

但是,如果只有第一个文件有标题行,则需要在第一个规则中进行FNR更改。NR

警告

正如 Ed Morton 所指出的,这种简单的方法仅在不同输出文件的数量很小(最多 10 个左右)时才有效。 GNUawk仍将继续工作,但由于根据需要在后台自动关闭和打开文件而变得更慢;其他awk实现可能会由于“打开的文件太多”而失败。

答案2

这将使用任何 awk、sort 和 cut 来稳健且高效地工作:

$ cat tst.sh
#!/usr/bin/env bash

awk 'BEGIN{FS=OFS="|"} {print (NR>1), $1, NR, $0}' "$@" |
sort -t'|' -k1,1n -k2,2 -k3,3n |
cut -d'|' -f4- |
awk '
    BEGIN { FS=OFS="|" }
    NR == 1 { hdr = $0; next }
    $1 != prev {
        close(prev)
        print hdr " > " $1
        prev = $1
    }
    { print $0 " > " $1 }
'

$ ./tst.sh a.txt
filename|count|age > 1.txt
1.txt|1|15 > 1.txt
1.txt|2|14 > 1.txt
filename|count|age > 2.txt
2.txt|3|1 > 2.txt
2.txt|1|3 > 2.txt
filename|count|age > 41.txt
41.txt|44|1 > 41.txt

更改" > "为仅>在完成测试时实际创建输出文件。

前导 awk|sort|cut 按文件名 ($1) 对所有输入行进行分组,以便最终的 awk 一次仅处理 1 个输出文件的内容,因此一次仅打开 1 个输出文件,因此获胜一旦在非 gawk 中创建了十几个左右的输出文件,或者由于使用 gawk 打开/关闭输出文件而导致运行速度变慢,则不会因“打开的文件名太多”错误而失败。

以下是每个早期阶段发生的情况,为最终 awk 脚本设置数据,以便能够解析它,同时仅打开 1 个输出文件,并在每个输出文件名的基础上保留原始输入顺序:

$ awk 'BEGIN{FS=OFS="|"} {print (NR>1), $1, NR, $0}' a.txt
0|filename|1|filename|count|age
1|1.txt|2|1.txt|1|15
1|1.txt|3|1.txt|2|14
1|2.txt|4|2.txt|3|1
1|41.txt|5|41.txt|44|1
1|2.txt|6|2.txt|1|3

$ awk 'BEGIN{FS=OFS="|"} {print (NR>1), $1, NR, $0}' a.txt |
    sort -t'|' -k1,1n -k2,2 -k3,3n
0|filename|1|filename|count|age
1|1.txt|2|1.txt|1|15
1|1.txt|3|1.txt|2|14
1|2.txt|4|2.txt|3|1
1|2.txt|6|2.txt|1|3
1|41.txt|5|41.txt|44|1

$ awk 'BEGIN{FS=OFS="|"} {print (NR>1), $1, NR, $0}' a.txt |
    sort -t'|' -k1,1n -k2,2 -k3,3n |
    cut -d'|' -f4-
filename|count|age
1.txt|1|15
1.txt|2|14
2.txt|3|1
2.txt|1|3
41.txt|44|1

相关内容