如何用awk和反向grep实现跨文件特定列的过滤?
用Bash实现基于双文件规则过滤TSV行
输入文件内容
制表符分隔的input1.tsv($IN1)
$ cat input1.tsv HEAD1 HEAD2 HEAD3 HEAD4 HEAD5 HEAD6 Qux ZX_999876 Bar Foo MN111111 Quux Foo AB_123456 Bar Baz CD789123 Qux Bar AC_456321 Baz Qux GF333444 Foo Foo CD789123 Qux Baz GH987124 Qux
逗号分隔的input2.csv($IN2)
$ cat input2.csv AB_123456,CD789123 ZX_999876,MN111111
过滤规则
若input2的第1列值等于input1的第2列值,则删除input1中所有第2列值等于该input2行第2列值的行。
期望输出
$ cat input1_filtered.tsv HEAD1 HEAD2 HEAD3 HEAD4 HEAD5 HEAD6 Qux ZX_999876 Bar Foo MN111111 Quux Foo AB_123456 Bar Baz CD789123 Qux Bar AC_456321 Baz Qux GF333444 Foo
现有脚本问题
用户尝试的脚本无法达成目标,脚本如下:
while IFS=, read -r CL1 CL2 do if awk -F"\t" '$2=="$CL1"' $IN1 then grep -vPw "(?<=\t)$CL2" $IN1 > tmp.txt mv tmp.txt $IN1 fi done < $IN2
问题点:
- 变量引用错误:awk单引号内的
$CL1不会被Shell解析,无法正确匹配input1的第2列; - 逻辑判断无效:
if awk ...是判断awk命令的退出状态,而非是否存在匹配行,且该写法会直接打印匹配行,不符合判断逻辑; - 文件修改不安全:循环中多次覆盖原文件
$IN1,容易导致数据丢失,且grep的-P选项依赖PCRE,并非所有系统支持,正则表达式也可能因制表符匹配问题出错; - 效率低下:循环处理每行input2,多次读写文件,对于大文件性能极差。
正确解决方案
推荐用awk一次性处理两个文件,逻辑清晰且效率更高:
awk -F'\t' ' BEGIN { # 读取input2,建立CL1到CL2的映射 while (getline < "'"$IN2"'") { FS="," split($0, pair, ",") cl1 = pair[1] cl2 = pair[2] map[cl1] = cl2 FS="\t" } } # 打印表头 NR == 1 { print; next } # 先标记需要删除的HEAD2值 $2 in map { to_delete[map[$2]] = 1 } # 仅保留不在删除列表中的行 !($2 in to_delete) { print } ' "$IN1" > input1_filtered.tsv
脚本说明
- 预处理input2:在
BEGIN块中读取input2,将每行的CL1和CL2存入映射表map; - 处理input1:先打印表头,然后遍历数据行:
- 若当前行的HEAD2(第2列)存在于
map的键中,就将对应的CL2标记为需要删除的目标; - 仅当当前行的HEAD2不在删除列表中时,才打印该行。
- 若当前行的HEAD2(第2列)存在于
这种写法无需修改原文件,一次性完成过滤,避免了循环读写的性能问题,且逻辑完全符合需求。
内容的提问来源于stack exchange,提问作者Michael Gruenstaeudl
相关产品推荐
相关产品推荐

