awk + bash: 组合任意数量的文件
awk + bash: combining arbitrary number of files
我有一个脚本,它采用许多布局相同但数据不同的数据文件,并将指定的数据列组合到一个新文件中,如下所示:
gawk '{
names[]= 1;
data[,ARGIND]=
} END {
for (i in names) print i"\t"data[i,1]"\t"data[i,2]"\t"data[i,3]
}' > combined_data.txt
...可以在第一列中找到行 ID,在第二列中找到有趣的数据。
这很好用,但不适用于任意数量的文件。虽然我可以简单地在最后一行添加 ... $n
直到我认为我需要的任何最大文件数,以及在上面的行中添加相等的 n
数量的 "\t"data[i,4]"\t"data[i,5] ... "\t"data[i,n]
(即使对于小于 n
的文件似乎也有效;在这些情况下,awk 似乎忽略了 n
大于输入文件的数量),这似乎是一个 "ugly" 解决方案。有没有办法让这个脚本(或给出相同结果的东西)接受任意数量的输入文件?
或者,更好的是,您能否以某种方式在其中加入一个 find
,搜索子文件夹并找到符合某些条件的文件?
这是一些示例数据:
文件.1
A 554
B 13
C 634
D 84
E 9
文件.2:
C TRUE
E TRUE
F FALSE
预期输出:
A 554
B 13
C 634 TRUE
D 84
E 9 TRUE
F FALSE
您可以通过 ARGV 列表上的重定向 getline 访问任意数量的文件(绕过 awk 的默认文件处理(通过 BEGIN 和 exit)):
awk 'BEGIN {
for(i=1;i<=ARGC;++i){
while (getline < ARGV[i]) {
...
}
}
<END-type code>
exit}' $(find -type f ...)
假设输入文件的命名模式为:1
2
....
gawk '{
names[]=
data[,ARGIND]=
}
END {
for (i in names) {
printf("%s\t",i)
for (x=1;x<=ARGIND;x++) {
printf("%s\t", data[i,x])
}
print ""
}
}' [0-9]* > combined_data.txt
结果:
A 554
B 13
C 634 TRUE
D 84
E 9 TRUE
F FALSE
这可能是您正在寻找的(使用 GNU awk 作为 ARGIND,就像您的原始脚本一样):
$ cat tst.awk
BEGIN { OFS="\t" }
!seen[]++ { keys[++numKeys]= }
{ vals[,ARGIND]= }
END {
for (rowNr=1; rowNr<=numKeys; rowNr++) {
key = keys[rowNr]
printf "%s%s", key, OFS
for (colNr=1; colNr<=ARGIND; colNr++) {
printf "%s%s", vals[key,colNr], (colNr<ARGIND?OFS:ORS)
}
}
}
$ awk -f tst.awk file1 file2
A 554
B 13
C 634 TRUE
D 84
E 9 TRUE
F FALSE
如果您不关心行的输出顺序,那么您只需要:
BEGIN { OFS="\t" }
{ vals[,ARGIND]=; keys[] }
END {
for (key in keys) {
printf "%s%s", key, OFS
for (colNr=1; colNr<=ARGIND; colNr++) {
printf "%s%s", vals[key,colNr], (colNr<ARGIND?OFS:ORS)
}
}
}
使用join
、bash
、awk
和tr
的另一种解决方案,如果file1
、file2
、file3
等排序
multijoin.sh
#!/bin/bash
function __t {
join -a1 -a2 -o '1.1 2.1 1.2 2.2' - "" |
awk -vFS='[ ]' '{print (!=""?:),"_";}';
}
CMD="cat ''"
for i in `seq 2 $#`; do
CMD="$CMD | __t '${@:$i:1}'";
done
eval "$CMD | tr '_' '\t' | tr ' ' '\t'";
或者,递归版本
#!/bin/bash
function __t {
join -a1 -a2 -o '1.1 2.1 1.2 2.2' - "" |
awk -vFS='[ ]' '{print (!=""?:),"_";}';
}
function __r {
if [[ "$#" -gt 1 ]]; then
__t "" | __r "${@:2}";
else
__t "";
fi
}
__r "${@:2}" < "" | tr '_' '\t' | tr ' ' '\t'
注意:数据不能包含字符_
,这被用作通配符
你明白了,
./multijoin file1 file2
A 554
B 13
C 634 TRUE
D 84
E 9 TRUE
F FALSE
for example, if file3
contains
A 111
D 222
E 333
./multijoin file1 file2 file3
你明白了,
A 554 111
B 13
C 634 TRUE
D 84 222
E 9 TRUE 333
F FALSE
我有一个脚本,它采用许多布局相同但数据不同的数据文件,并将指定的数据列组合到一个新文件中,如下所示:
gawk '{
names[]= 1;
data[,ARGIND]=
} END {
for (i in names) print i"\t"data[i,1]"\t"data[i,2]"\t"data[i,3]
}' > combined_data.txt
...可以在第一列中找到行 ID,在第二列中找到有趣的数据。
这很好用,但不适用于任意数量的文件。虽然我可以简单地在最后一行添加 ... $n
直到我认为我需要的任何最大文件数,以及在上面的行中添加相等的 n
数量的 "\t"data[i,4]"\t"data[i,5] ... "\t"data[i,n]
(即使对于小于 n
的文件似乎也有效;在这些情况下,awk 似乎忽略了 n
大于输入文件的数量),这似乎是一个 "ugly" 解决方案。有没有办法让这个脚本(或给出相同结果的东西)接受任意数量的输入文件?
或者,更好的是,您能否以某种方式在其中加入一个 find
,搜索子文件夹并找到符合某些条件的文件?
这是一些示例数据:
文件.1
A 554
B 13
C 634
D 84
E 9
文件.2:
C TRUE
E TRUE
F FALSE
预期输出:
A 554
B 13
C 634 TRUE
D 84
E 9 TRUE
F FALSE
您可以通过 ARGV 列表上的重定向 getline 访问任意数量的文件(绕过 awk 的默认文件处理(通过 BEGIN 和 exit)):
awk 'BEGIN {
for(i=1;i<=ARGC;++i){
while (getline < ARGV[i]) {
...
}
}
<END-type code>
exit}' $(find -type f ...)
假设输入文件的命名模式为:1
2
....
gawk '{
names[]=
data[,ARGIND]=
}
END {
for (i in names) {
printf("%s\t",i)
for (x=1;x<=ARGIND;x++) {
printf("%s\t", data[i,x])
}
print ""
}
}' [0-9]* > combined_data.txt
结果:
A 554
B 13
C 634 TRUE
D 84
E 9 TRUE
F FALSE
这可能是您正在寻找的(使用 GNU awk 作为 ARGIND,就像您的原始脚本一样):
$ cat tst.awk
BEGIN { OFS="\t" }
!seen[]++ { keys[++numKeys]= }
{ vals[,ARGIND]= }
END {
for (rowNr=1; rowNr<=numKeys; rowNr++) {
key = keys[rowNr]
printf "%s%s", key, OFS
for (colNr=1; colNr<=ARGIND; colNr++) {
printf "%s%s", vals[key,colNr], (colNr<ARGIND?OFS:ORS)
}
}
}
$ awk -f tst.awk file1 file2
A 554
B 13
C 634 TRUE
D 84
E 9 TRUE
F FALSE
如果您不关心行的输出顺序,那么您只需要:
BEGIN { OFS="\t" }
{ vals[,ARGIND]=; keys[] }
END {
for (key in keys) {
printf "%s%s", key, OFS
for (colNr=1; colNr<=ARGIND; colNr++) {
printf "%s%s", vals[key,colNr], (colNr<ARGIND?OFS:ORS)
}
}
}
使用join
、bash
、awk
和tr
的另一种解决方案,如果file1
、file2
、file3
等排序
multijoin.sh
#!/bin/bash
function __t {
join -a1 -a2 -o '1.1 2.1 1.2 2.2' - "" |
awk -vFS='[ ]' '{print (!=""?:),"_";}';
}
CMD="cat ''"
for i in `seq 2 $#`; do
CMD="$CMD | __t '${@:$i:1}'";
done
eval "$CMD | tr '_' '\t' | tr ' ' '\t'";
或者,递归版本
#!/bin/bash
function __t {
join -a1 -a2 -o '1.1 2.1 1.2 2.2' - "" |
awk -vFS='[ ]' '{print (!=""?:),"_";}';
}
function __r {
if [[ "$#" -gt 1 ]]; then
__t "" | __r "${@:2}";
else
__t "";
fi
}
__r "${@:2}" < "" | tr '_' '\t' | tr ' ' '\t'
注意:数据不能包含字符_
,这被用作通配符
你明白了,
./multijoin file1 file2
A 554 B 13 C 634 TRUE D 84 E 9 TRUE F FALSE
for example, if
file3
contains
A 111 D 222 E 333
./multijoin file1 file2 file3
你明白了,
A 554 111 B 13 C 634 TRUE D 84 222 E 9 TRUE 333 F FALSE