我需要比较两个包含多行的 txt 文件,并创建第三个 txt 文件,其中仅包含文件 1 特有的行。文件 1 的示例如下:
../../A/folder/fname.gz | -12.36 | A:BCD:123:A, D:DFR:241:AZ1
../../A/folder/fname2.gz | -4.56 | B:ABC:456:C | G:RFT:265:T
它持续了数千行,而文件 2 的示例可能是:
../../B/folder2/fname.gz | -7.65 | C:ABC:425:A
../../B/folder2/fname3.gz | -12.31 | A:BCD:758:D
我需要从文件 1 中获取基于第一个字段唯一的所有行(例如../../folder/fname2.gz
在示例中)。请注意folder
,可以不同,但fnameX.gz
应该是唯一的。folder
和都fname
包含-
和/或_
。每行中的字段数可以不同。上面示例的预期输出应该是:
../../A/folder/fname2.gz | -4.56 | B:ABC:456:C | G:RFT:265:T
做这个的最好方式是什么?
答案1
awk -F ' *[|] *' '{ k=$1; sub(".*/", "", k) }
!z { a[k]; next } !( k in a )' file2 z=1 file1
首先读取file2
,将文件名部分存储在数组中。读取时file1
,如果数组中不存在文件名,则打印一行。
答案2
我们可以使用一个施瓦茨变换和一个辅助 shell 函数,并假设每个文件的第一个字段中的文件名不包含|
字符,并且字段由|
可选空格(空格和/或制表符)包围。
我们首先定义一个辅助函数 ,sorter
它从输入数据的第一个字段中提取文件名,并在对数据进行排序之前在每一行中添加此文件名前缀:
sorter () {
awk -F '[[:blank:]]*\|[[:blank:]]*' -v OFS='|' \
'{ key=$1; sub(".*/","",key); print key, $0 }' |
sort
}
$0
如果您只需要最终输出中第一个字段的路径名,请更改$1
上面代码中的 。将其更改$2
为仅获取第二个字段等。
数据需要按文件名排序,因为join
实用程序假定其输入是按连接字段排序的。
该函数从其标准输入中读取数据,如果我们只通过它运行第一个文件,则结果如下:
$ sorter <file1
fname.gz|../../A/folder/fname.gz | -12.36 | A:BCD:123:A, D:DFR:241:AZ1
fname2.gz|../../A/folder/fname2.gz | -4.56 | B:ABC:456:C | G:RFT:265:T
在每个输入文件上执行此操作并使用join
连接第一个字段,我们可能会要求join
仅输出第一个文件中无法与第二个文件中的任何行配对的行,使用join -v 1
:
join -t '|' -v 1 <( sorter <file1 ) <( sorter <file2 ) | cut -d '|' -f 2-
最后,该命令删除我们的命令在每行添加的cut
文件名字段。awk
考虑到问题中的数据,结果将是
../../A/folder/fname2.gz | -4.56 | B:ABC:456:C | G:RFT:265:T
在代码中$0
替换为,你会得到$1
awk
../../A/folder/fname2.gz