使用AWK按列名从空格分隔文件筛选列的问题
空格分隔压缩文件的列筛选脚本适配方案
问题场景
有一个空格分隔的压缩文件majorfile.gz,部分内容如下:
zcat majorfile.gz | head -n 3 | cut -d ' ' -f1-10
输出:
marker alleleA alleleB FINCH_WB_633_splitMerged FINCH_WB_633_splitMerged FINCH_WB_633_splitMerged FINCH_WB_C049985_splitMerged FINCH_WB_C049985_splitMerged FINCH_WB_C049985_splitMerged FINCH_WB_C071898_splitMerged LR761571.1_34273 G C 0.9955 0.0045 0 0.9996 0.0004 0 1 LR761571.1_34285 G A 0.9934 0.0066 0 0.9999 0.0001 0 0.9435
需要根据header.subset.txt中的列名筛选列,该文件部分内容:
cat header.subset.txt | head
输出:
marker alleleA alleleB FINCH_WB_633_splitMerged FINCH_WB_ES1B002_splitMerged FINCH_WB_JH1417_splitMerged FINCH_WB_JH1452_splitMerged FINCH_WB_JH1495_splitMerged FINCH_WB_JP000_splitMerged FINCH_WB_JP004_splitMerged
原循环脚本处理制表符分隔文件正常,但处理空格分隔文件时,会保留表头中所有同名的目标列(比如FINCH_WB_633_splitMerged出现三次就全部保留),且输出行末尾存在多余空格,结果不符合预期:
marker alleleA alleleB FINCH_WB_633_splitMerged FINCH_WB_633_splitMerged FINCH_WB_633_splitMerged LR761571.1_34273 G C 0.9955 0.0045 0 LR761571.1_34285 G A 0.9934 0.0066 0
原AWK脚本(格式化后):
NR == FNR { a[$1]++ next } { if (FNR == 1) { for (i = 1; i <= NF; i++) { if (a[$i]) { printf $i " " b[i] = $i } } } else { printf "\n" for (j = 1; j <= NF; j++) { if (b[j]) { printf $j " " } } } } END { printf "\n" }
问题原因
- 原脚本用
a[$1]++统计列名出现次数,表头中重复列名会被多次匹配,导致所有同名列都被保留 - 输出逻辑直接追加空格,导致每行末尾存在多余空格
- 部分AWK版本不支持直接读取
.gz压缩文件,存在兼容性隐患
解决方案
根据需求提供两种适配方案:
方案1:保留每个目标列名的第一次出现
如果需要对重复列名只保留表头中第一次出现的列,修改后的AWK脚本如下:
NR == FNR { target_cols[$1] = 1 next } FNR == 1 { # 记录需要保留的列索引,匹配后清除标记避免重复选中 for (i = 1; i <= NF; i++) { if (target_cols[$i]) { keep_cols[i] = 1 delete target_cols[$i] } } # 输出表头,避免末尾空格 first = 1 for (i = 1; i <= NF; i++) { if (keep_cols[i]) { printf "%s%s", first ? "" : " ", $i first = 0 } } printf "\n" next } # 处理数据行 { first = 1 for (i = 1; i <= NF; i++) { if (keep_cols[i]) { printf "%s%s", first ? "" : " ", $i first = 0 } } printf "\n" }
方案2:保留所有匹配的列(包括同名列)
如果需要保留表头中所有同名的目标列,只需移除方案1中的delete target_cols[$i]即可:
NR == FNR { target_cols[$1] = 1 next } FNR == 1 { # 记录所有需要保留的列索引 for (i = 1; i <= NF; i++) { if (target_cols[$i]) { keep_cols[i] = 1 } } # 输出表头,避免末尾空格 first = 1 for (i = 1; i <= NF; i++) { if (keep_cols[i]) { printf "%s%s", first ? "" : " ", $i first = 0 } } printf "\n" next } # 处理数据行 { first = 1 for (i = 1; i <= NF; i++) { if (keep_cols[i]) { printf "%s%s", first ? "" : " ", $i first = 0 } } printf "\n" }
优化后的循环脚本
结合压缩文件读取兼容性和批量处理需求,修改循环脚本:
for subset_file in header.subset.txt; do # 生成带后缀的输出文件名 output_file="majorfile_$(basename "$subset_file" .txt).gz" # 执行筛选并压缩输出 awk ' NR == FNR { target_cols[$1] = 1 next } FNR == 1 { for (i = 1; i <= NF; i++) { if (target_cols[$i]) { keep_cols[i] = 1 # 如需保留唯一列,取消下面一行注释 # delete target_cols[$i] } } first = 1 for (i = 1; i <= NF; i++) { if (keep_cols[i]) { printf "%s%s", first ? "" : " ", $i first = 0 } } printf "\n" next } { first = 1 for (i = 1; i <= NF; i++) { if (keep_cols[i]) { printf "%s%s", first ? "" : " ", $i first = 0 } } printf "\n" }' "$subset_file" <(zcat majorfile.gz) | gzip > "$output_file" done
关键优化点
- 用
target_cols[$1] = 1替代计数逻辑,简化匹配判断 - 新增
first标记控制空格输出,消除行尾多余空格 - 可选的
delete target_cols[$i]实现唯一列保留 - 用
<(zcat majorfile.gz)显式读取压缩文件,提升跨版本兼容性
内容的提问来源于stack exchange,提问作者M. Beausoleil
相关产品推荐
相关产品推荐

