我有一个看起来有点像这样的文件:
C1 C2 C3 C4 C5
0 0 0 0 0
0 1 0 0 0
0 0 0 1 0
0 0 0 0 0
但更大...
我只想提取其中包含所有 0 的列,因此我的输出文件应如下所示:
C1 C3 C5
0 0 0
0 0 0
0 0 0
0 0 0
这可以用一个简单的 awk 单行来完成吗(类似于awk:例如基于另一列的值打印列)?如果不是,是否有另一种方法可以使用 bash 有效地做到这一点?
我有一个看起来有点像这样的文件:
C1 C2 C3 C4 C5
0 0 0 0 0
0 1 0 0 0
0 0 0 1 0
0 0 0 0 0
但更大...
我只想提取其中包含所有 0 的列,因此我的输出文件应如下所示:
C1 C3 C5
0 0 0
0 0 0
0 0 0
0 0 0
这可以用一个简单的 awk 单行来完成吗(类似于awk:例如基于另一列的值打印列)?如果不是,是否有另一种方法可以使用 bash 有效地做到这一点?
尝试关注awk
awk 'NR==1 {next} NR==FNR { for(i=1;i<=NF;i++) sum[i]+=$i; next } { for(i=1;i<=NF;i++) if (sum[i]==0) printf " %s", $i; print "" }' file{,}
输出
C1 C3 C5
0 0 0
0 0 0
0 0 0
0 0 0
这里的想法是对文件进行两次迭代。一旦它计算出所有列的总和,并且在下一次迭代中,它只打印总和为零的列。
这假设所有列条目都只有正数
另一种可能更好的方法是,如果列中的任何条目非零,则设置一个标志。然后只打印对应标志为零的那些列。
awk 'NR==1 {next} NR==FNR { for(i=1;i<=NF;i++) if ($i) flag[i]=1; next } { for(i=1;i<=NF;i++) if (!flag[i]) printf " %s", $i; print "" }' file{,}
这种方法允许正数和负数,并消除了任何限制。
或者正如@fedorqui在评论中所建议的那样
awk 'NR==1 {next} NR==FNR { for(i=1;i<=NF;i++) if ($i) flag[i]=1; next } { for(i=1;i<=NF;i++) if (flag[i]) $i="" } 1' file{,}
这适用于带有负数的数据或其他字符串,如 ' foo
' 或 ' bar
'
单线:
awk 'NR==1{next}NR==FNR{while(++i<=NF)if($i!="0")k[i];i=0;next}{while(++x<=NF)if(!(x in k))printf "%s ",$x;x=0;print ""}' file file
更具可读性:
awk 'NR==1{next}
NR==FNR{while(++i<=NF)if($i!="0")k[i];i=0;next}
{while(++x<=NF)
if(!(x in k)) printf "%s ",$x
x=0
print ""}' file file
一个很长的解决方案。
将列转换为行
awk '{
for (f = 1; f <= NF; f++) { a[NR, f] = $f }
}
NF > nf { nf = NF }
END {
for (f = 1; f <= nf; f++) {
for (r = 1; r <= NR; r++) {
printf a[r, f] (r==NR ? RS : FS)
}
}
}' file >tmp1
仅打印仅包含的行0
awk '{for (i=2;i<=NF;i++) f+=$i} !f; {f=0}' tmp1 >tmp2
转换回来
awk '{
for (f = 1; f <= NF; f++) { a[NR, f] = $f }
}
NF > nf { nf = NF }
END {
for (f = 1; f <= nf; f++) {
for (r = 1; r <= NR; r++) {
printf a[r, f] (r==NR ? RS : FS)
}
}
}' tmp2
给
C1 C3 C5
0 0 0
0 0 0
0 0 0
0 0 0