Bash脚本按唯一值条件筛选CSV列的实现及脚本问题排查
问题根因定位
- Shell重定向执行逻辑的固有坑:你写的
paste -d, newfile.csv <(cut -d, -f$i file1.csv) > newfile.csv这条命令,Shell会在启动paste进程之前,先把输出目标newfile.csv截断清空。这时候paste进程去读已经被清空的newfile.csv,自然拼不出预期的多列内容,这也是为什么你最后看到的输出文件,永远只保留最后一次循环处理的那一列内容的核心原因。你之前尝试用临时文件中转没解决问题,大概率是循环里依然存在读写同一个目标文件的操作,没有等所有列的筛选判断完成,再一次性生成最终文件。 - 唯一值统计逻辑错误:原脚本用
uniq -u统计列唯一值数量的方法完全不适用。uniq -u的作用是仅输出连续出现、且全程没有重复的行,根本不是统计列的总唯一值数。比如某列所有值都是ah,用uniq -u计算会得到0(因为所有值都重复,没有符合“仅出现一次”要求的行),但这列实际唯一值数是1,应该被过滤。正确的列唯一值统计方法,是先对列值排序去重再计数,也就是cut ... | sort -u | wc -l。 - 筛选逻辑不严谨:原脚本一开始就硬把第一列写入输出文件,循环只从第2列开始遍历,没有对第一列做唯一值数判断,完全不符合“仅当列唯一值数大于1才纳入”的统一筛选规则。
- 逐列拼接中间结果的方式稳定性差,大文件场景下会产生大量不必要的IO开销。
修正后的可用脚本
下面的脚本完全避开了边读边写的坑,统计逻辑准确,同时适配你给出的示例场景:
#!/bin/bash input_file="file1.csv" output_file="newfile.csv" # 获取文件总列数 max_col=$(head -n1 "$input_file" | awk -F',' '{print NF}') qualified_cols=() # 逐列判断是否满足「唯一值数>1」的筛选条件 for ((i=1; i<=max_col; i++)); do unique_cnt=$(cut -d',' -f"$i" "$input_file" | sort -u | wc -l) if [ "$unique_cnt" -gt 1 ]; then qualified_cols+=("$i") else echo "第${i}列唯一值数为${unique_cnt},不纳入最终输出" fi done # 一次性提取所有符合条件的列,直接生成目标文件 cut -d',' -f"$(IFS=,; echo "${qualified_cols[*]}")" "$input_file" > "$output_file"
运行效果
针对你给出的示例输入,脚本会识别出:
- 第2列所有值都是
ah,唯一值数为1,被过滤 - 第4列所有值都是
ab,唯一值数为1,被过滤 - 第1、3、5列唯一值数都大于1,全部纳入输出,最终生成的
newfile.csv和你预期的内容完全一致。
如果后续遇到GB级以上的超大文件,可以改用awk一次性读入全表统计每列的唯一值,进一步减少IO次数。对刚接触Bash的开发者来说,上面的脚本逻辑简单、排错容易,足够覆盖绝大多数业务场景。
内容的提问来源于stack exchange,提问作者Trex
相关产品推荐
相关产品推荐

