如何让Bash列重排脚本适配不同分隔符?
修正支持多分隔符的Bash列重排脚本
你的核心问题是分隔符未处理正则特殊字符(比如#是awk注释符,,是默认字段分隔符的一部分),加上可能的参数顺序错误,导致非TSV场景失效。以下是修正后的脚本及关键说明:
修正后的脚本
#!/bin/bash # 参数校验:输入文件、输出文件、分隔符、discard选项、至少一个列名 if [ $# -lt 5 ]; then echo "用法: $0 <输入文件> <输出文件> <分隔符> <discard(yes/no)> <列名1> [列名2...]" exit 1 fi # 解析参数 INPUT_FILE="$1" OUTPUT_FILE="$2" DELIMITER="$3" DISCARD="$4" shift 4 TARGET_COLS=("$@") # 转义分隔符中的正则特殊字符(如# . * + ? ^ $等) ESCAPED_DELIM=$(printf '%s' "$DELIMITER" | sed 's/[.*+?^$(){}|[\]\\]/\\&/g') # 核心awk处理逻辑 awk -v raw_delim="$DELIMITER" -v regex_delim="$ESCAPED_DELIM" -v discard="$DISCARD" -v target_cols="${TARGET_COLS[*]}" ' BEGIN { FS = regex_delim # 用转义后的分隔符做字段分割 OFS = raw_delim # 输出用原始分隔符,保证格式一致 split(target_cols, col_list, / /) # 将目标列名转为数组 col_count = length(col_list) } # 处理表头:建立列名->列号的映射 NR == 1 { for (i = 1; i <= NF; i++) { name_to_idx[$i] = i } # 输出指定列 for (i = 1; i <= col_count; i++) { printf "%s%s", (i>1?OFS:""), col_list[i] } # 不丢弃其余列时,追加未指定的列 if (discard == "no") { for (i = 1; i <= NF; i++) { skip = 0 for (j = 1; j <= col_count; j++) { if ($i == col_list[j]) { skip = 1 break } } if (!skip) { printf "%s%s", OFS, $i } } } print "" next } # 处理数据行 { # 输出指定列 for (i = 1; i <= col_count; i++) { printf "%s%s", (i>1?OFS:""), $(name_to_idx[col_list[i]]) } # 不丢弃其余列时,追加未指定的列 if (discard == "no") { for (i = 1; i <= NF; i++) { skip = 0 for (j = 1; j <= col_count; j++) { if ($i == col_list[j]) { skip = 1 break } } if (!skip) { printf "%s%s", OFS, $i } } } print "" } ' "$INPUT_FILE" > "$OUTPUT_FILE"
关键修正点
- 分隔符转义:用
sed处理分隔符中的正则特殊字符,避免awk把#当成注释、.当成任意字符,保证字段分割正确。 - 明确参数顺序:强制参数顺序为
输入文件→输出文件→分隔符→discard选项→列名,避免参数错位。 - FS/OFS分离:FS用转义后的正则分隔符做字段分割,OFS用原始分隔符输出,保证输出格式和输入一致。
- 去重追加逻辑:处理非丢弃场景时,跳过已指定的列,避免重复输出。
针对你的示例的正确用法
注意分隔符需要用引号括起来,避免Shell解析错误:
bash arrange.sh hash.tsv hash_rearranged.tsv "#" no NUM1 CHR
执行后输出文件hash_rearranged.tsv内容:
NUM1#CHR#NUM2 9999999#chr22#10001000 10009999#chr22#10010100
内容的提问来源于stack exchange,提问作者sasa.asa
相关产品推荐
相关产品推荐

