好的,我会尽力解释我需要做什么。基本上我有两个 CSV 文件,如下例所示:
文件一:
Column 1, Column 2
abc , 123
def , 234
adf , 567
文件2
Column 1, Column 2
abc , 123
def , 234
adf , 578
我需要编写一个 shell 脚本或简单命令来执行以下操作:
- 按第 1 列对两个文件进行排序
- 逐行执行以下操作:
- 使用文件 1 中的第 1 列,在文件 2 的第 1 列中搜索该值。
- 如果找到,则将文件 1 第 2 列中的值与文件 2 第 2 列中的值进行比较
- 如果匹配,则将第 1 列、第 2 列和第 3 列中的“已验证”写入单独的文件
- 如果不匹配,则将第 1 列、第 2 列和“失败”写入单独的文件
- 使用文件 1 中的第 1 列,在文件 2 的第 1 列中搜索该值。
这会产生两个输出文件:第一个包含在第 1 列和第 2 列中找到的所有内容匹配,第二个文件包含失败的第 1 列查找或找到第 1 列但第 2 列不匹配的位置,因此,本质上,使用第 1 列作为检查第 2 列的键。
答案1
给定以下输入文件:
$ cat in1 in2
Column 1, Column 2
abc , 123
def , 234
adf , 567
Column 1, Column 2
abc , 123
def , 234
adf , 578
首先,我们对它们进行排序;然后我们可以将它们拼接成一个文件:
$ sort in1 > in1.sorted; sort in2 > in2.sorted; paste in{1,2}.sorted
Column 1, Column 2 Column 1, Column 2
abc , 123 abc , 123
adf , 567 adf , 578
def , 234 def , 234
awk
在这里会帮助我们,但是逗号会妨碍我们;我们可以首先摆脱它们sed
:
$ paste in{1,2}.sorted | sed s/,//g
Column 1 Column 2 Column 1 Column 2
abc 123 abc 123
adf 567 adf 578
def 234 def 234
然后我们可以通过快速转储它awk
:
$ paste in{1,2}.sorted | sed s/,//g | awk '$2 == $4 {print $1,"Validated"}; $2 != $4 { print $1,"Failed"}'
Column Failed
abc Validated
adf Failed
def Validated
这也可以使用 raw 来完成awk
,优点是能够去掉标题行,并且不依赖于相同顺序的相同数据,从而消除了排序的需要:
$ awk 'FNR != 1 && NR == FNR {data[$1]=$3} FNR != 1 && NR != FNR {if(data[$1]==$3) {print $1, "Validated"} else {print $1, "Failed"} }' in{1,2}
abc Validated
adf Failed
def Validated
这依赖于一些神奇的awk
内置变量和与之相关的技巧:
NR
- 处理的记录总数FNR
- 记录总数在当前文件中处理FNR != 1
- 跳过每个文件的第一行(不将标题视为数据)NR != FNR
- 仅在第一个文件完全读取并且我们已开始读取后续文件后运行。这允许我们data
在开始咀嚼第二个文件后预先填充数组以进行测试。
答案2
我想我现在已经通过以下内容解决了这个问题,以防其他人读到这个并需要这个。再次感谢。
FNR == NR {
for (i = 2; i <= NF; i++) { a[i,$1] = $i }
b[$1];
next;
}
($1 in b) { # check if row in file2 existed in file1
for (i = 2; i <= NF; i++) {
if (a[i,$1] == $i)
printf("%s->col%d: %s vs %s: Valid\n", $1, i-1, a[i,$1], $i);
else
printf("%s->col%d: %s vs %s: Failure\n", $1, i-1, a[i,$1], $i);
}
delete b[$1]; # delete entries which are processed
}
END {
for (left in b) { # look which didn't match
for (i = 2; i <= NF; i++)
printf("%s->col%d: %s vs (blank): Not Equal\n", left, i-1, a[i,left])
}
}
答案3
使用乐(以前称为 Perl_6)
#! /usr/bin/env raku
#INPUT AND HEADERS:
my $csv1 = "Veyron1.txt".IO;
my $csv2 = "Veyron2.txt".IO;
my $hdr1 = "Key,Value,Verified";
my $hdr2 = "Key,Value,Failed";
#HASH STORAGE (Below, beware of `skip`ping header in headerless file!):
my %csv1; for $csv1.lines.skip.map( *.split(",").map( *.trim)) {
%csv1.push: .[0] => .[1]
};
my %csv2; for $csv2.lines.skip.map( *.split(",").map( *.trim)) {
%csv2.push: .[0] => .[1]
};
#SANITY CHECKS:
die "multiple values per key in file_1" if any(%csv1.values.map: *.elems > 1).so;
die "multiple values per key in file_2" if any(%csv2.values.map: *.elems > 1).so;
#OUTPUT FILE PREP W/ HEADER:
!("Veyron_output_verified.txt".IO.e) && (my $fh1 = "Veyron_output_verified.txt".IO.open: :a);
!("Veyron_output_failed.txt".IO.e) && (my $fh2 = "Veyron_output_failed.txt".IO.open: :a);
$fh1.put: $hdr1;
$fh2.put: $hdr2;
#OUTPUT LOOP:
for %csv1.keys.sort -> $id {
if %csv2{$id}:exists {
if %csv1{$id} eq %csv2{$id} {
$fh1.put: ($id, %csv1{$id}, "verified").join: ",";
}
else {
$fh2.put: ($id, %csv1{$id}, "mismatch").join: ",";
}
}
else {
$fh2.put: ($id, %csv1{$id}, "absent").join: ",";
}
}
$fh1.close;
$fh2.close;
这是用 Raku(Perl 系列编程语言)编写的脚本。与 Perl 一样,Raku 具有键/值数据结构以及各种文件操作符,这使得它非常适合解决此类性质的问题。简要地:
- 顶部的
$
-sigiled$csv1
表示$csv2
文件句柄(实际上是.IO
对象)。因为符号在 Raku 中保持不变...... - 两个
%
签名哈希值%csv1
和%csv2
分别表示从每个文件获取的键/值的存储位置。 - 修剪空白、标头操作和健全性检查为运行代码增加了细节。
- 文件运算符(
.e
例如“exists”)可确保预先存在的输出文件不会被覆盖。文件句柄用:a
选项打开,代表:append
. - 在输出循环中,
%csv1
在 , 中查找键来%csv2
检查字符串等价(与eq
) 同源value
s.返回三个字符串之一:“已验证”、“不匹配”或“不存在”。 - 输出按行附加到打开的输出文件句柄,该句柄在脚本末尾关闭。
注意:如果您想检查数值等价每个键的值,替换:
%csv1{$id} eq %csv2{$id}
和: %csv1{$id} == %csv2{$id}
示例输出(“已验证”):
Key,Value,Verified
abc,123,verified
def,234,verified
示例输出(“失败”):
Key,Value,Failed
adf,567,mismatch
乐库参考资料:
https://docs.raku.org
https://raku.org
相关 Perl 脚本:
https://www.perlmonks.org/?node_id=805106