我有一个管道分隔文件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