我有一个包含许多来自表导出的 CSV 文件的目录
tblA.csv
A,B,C
1,1,1
1,2,2
2,2,2
3,3,3
Run Code Online (Sandbox Code Playgroud)
tblB.csv
C,D,A
1,1,1
1,2,2
2,2,2
3,3,3
Run Code Online (Sandbox Code Playgroud)
为了打破文件,我找到了这个脚本
awk -F, '
NR== 1 { hdr = $0;next}
{out = "File" $1 ".csv"}
printed[$1]++<1 {print hdr >out}
{print $0 > out}
' tblA.csv
Run Code Online (Sandbox Code Playgroud)
创建 3 个文件
A,B,C
1,1,1
1,2,2
Run Code Online (Sandbox Code Playgroud)
A,B,C
2,2,2
Run Code Online (Sandbox Code Playgroud)
A,B,C
3,3,3
Run Code Online (Sandbox Code Playgroud)
现在对于 tblB.csv 我仍然需要按列 A 打破文件,但该列是第 3 列而不是第 1 列
我想不出一种方法来传递参数 A 让它循环标题以找到与我传递的参数匹配的列名,然后使用该列值破坏文件。或者,如果该列名不存在,则跳过该文件。
以下是按名称打印列的方法:
按名称打印列:
$ cat tst.awk
BEGIN { FS="," }
NR==1 {
for (i=1; i<=NF; i++) {
f[$i] = i
}
}
{ print $(f["A"]) }
Run Code Online (Sandbox Code Playgroud)
$ awk -f tst.awk tblA.csv
A
1
1
2
3
Run Code Online (Sandbox Code Playgroud)
$ awk -f tst.awk tblB.csv
A
1
2
2
3
Run Code Online (Sandbox Code Playgroud)
以下是如何使用该习惯用法在每个 Unix 机器上的任何 shell 中使用任何 awk* 来稳健有效地执行您要求的操作:
按分组键值拆分输入文件:
$ cat tst.awk
BEGIN { FS="," }
NR==1 {
for (i=1; i<=NF; i++) {
f[$i] = i
}
hdr = $0
next
}
!(tgt in f) { exit }
{ curr = $(f[tgt]) }
curr != prev {
close(out)
out = "File" curr ".csv"
print hdr > out
prev = curr
}
{ print > out }
Run Code Online (Sandbox Code Playgroud)
$ awk -v tgt='A' -f tst.awk tblA.csv
Run Code Online (Sandbox Code Playgroud)
$ head File*.csv
==> File1.csv <==
A,B,C
1,1,1
1,2,2
==> File2.csv <==
A,B,C
2,2,2
==> File3.csv <==
A,B,C
3,3,3
Run Code Online (Sandbox Code Playgroud)
$ awk -v tgt='A' -f tst.awk tblB.csv
Run Code Online (Sandbox Code Playgroud)
$ head File*.csv
==> File1.csv <==
C,D,A
1,1,1
==> File2.csv <==
C,D,A
1,2,2
2,2,2
==> File3.csv <==
C,D,A
3,3,3
Run Code Online (Sandbox Code Playgroud)
以上假设输入文件按示例输入中所示的键字段分组 - 如果不是,则可以在 awk 脚本中处理:
仅使用 AWK 按非分组键值拆分输入文件:
$ cat tblC.csv
C,D,A
2,2,3
1,2,2
1,1,3
3,3,1
Run Code Online (Sandbox Code Playgroud)
$ cat tst.awk
BEGIN { FS="," }
NR==1 {
for (i=1; i<=NF; i++) {
f[$i] = i
}
hdr = $0
next
}
{ curr = $(f[tgt]) }
curr != prev {
close(out)
out = "File" curr ".csv"
if ( !doneHdr[curr]++ ) {
print hdr > out
}
prev = curr
}
{ print >> out }
Run Code Online (Sandbox Code Playgroud)
$ awk -v tgt='A' -f tst.awk tblC.csv
Run Code Online (Sandbox Code Playgroud)
$ head File*.csv
==> File1.csv <==
C,D,A
3,3,1
==> File2.csv <==
C,D,A
1,2,2
==> File3.csv <==
C,D,A
2,2,3
1,1,3
Run Code Online (Sandbox Code Playgroud)
但是如果您的文件很大,则对它们进行排序会更有效,以便在运行 awk 脚本之前对键值进行分组,这样 awk 就不必重复打开/关闭输出文件:
使用 sort+AWK 按非分组键值拆分输入文件(对大文件更有效):
$ cat tst.sh
#!/usr/bin/env bash
tgt="$1"
shift
awk -v tgt="$tgt" '
BEGIN { FS=","; OFS="\t" }
NR==1 {
for (i=1; i<=NF; i++) {
f[$i] = i
}
}
{ print (NR>1), $(f[tgt]), NR, $0 }
' "${@:--}" |
sort -k1,1n -k2,2 -k3,3n |
cut -f4- |
awk -v tgt="$tgt" '
BEGIN { FS="," }
NR==1 {
for (i=1; i<=NF; i++) {
f[$i] = i
}
hdr = $0
next
}
{ curr = $(f[tgt]) }
curr != prev {
close(out)
out = "File" curr ".csv"
print hdr > out
prev = curr
}
{ print > out }
'
Run Code Online (Sandbox Code Playgroud)
$ ./tst.sh 'A' tblC.csv
Run Code Online (Sandbox Code Playgroud)
$ head File*.csv
==> File1.csv <==
C,D,A
3,3,1
==> File2.csv <==
C,D,A
1,2,2
==> File3.csv <==
C,D,A
2,2,3
1,1,3
Run Code Online (Sandbox Code Playgroud)
上面的工作首先使用 awk 通过在每一行前面添加来装饰原始输入:
NR>1= 是否标头,0 或 1 指示符,因此我们可以确保标头始终在sort,$(f[tgt]) = 我们要排序的键值,NR= 当前行号,因此我们得到与重复键输入相同的顺序输出(也可以不添加,然后使用 GNU sort for -s)然后我们对这些字段进行排序,然后在主 awk 脚本开始创建输出文件之前使用cut(可以在随后的 awk 脚本中这样做但cut很有效并避免混乱)再次删除它们。
在此类 AWK 脚本中需要注意的事项:
* 如果您得到其他答案以执行以下任一操作,请注意:
close()一旦超过可能低至 15 的阈值,任何没有输出文件的解决方案都会在大多数 awk 中因“打开的文件太多”而失败,甚至支持无限“打开”文件的 awk,例如 GNU awk 也会失败放慢超过该阈值的速度,因为它必须通过在幕后实际需要在操作系统中打开/关闭它们来管理所有这些“打开”文件,并且print > "File" $1 ".csv"在右侧的表达式周围使用或类似但没有括号的解决方案>在大多数 awks 中都将失败并出现语法错误,因为这是未定义的行为。