我有两个文件:
F1.txt
:
a:erwer|ee
b:eeeee|eee
c:ewrew|erwe
和F2.txt
:
a.T1
a.T2
b.T3
C.T7
c.T4
我需要通过检查 F2.txt 中的 F1.txt) 来检查a
, b
,关键字出现的次数。c
F1.txt 中的预期输出:
a:erwer|ee:total:2
b:eeeee|eeet:total:1
c:ewrew|erwe:total:2
更新另一个文件中的o/p:
a:2
b:1
c:2
答案1
如果您的文件不太大,您可以使用 awk :
awk '
BEGIN{FS=".";OFS=":"}
NR==FNR{a[tolower($0)]=0;next}
{
if(tolower($1) in a){
a[tolower($1)]++
}
}
END{
for(key in a){
print key, a[key]
}
}
' F1.txt F2.txt
如果您想要区分大小写的内容,请删除该tolower
功能。
对于您编辑的问题:
awk '
BEGIN{FS="[:.]";OFS=":"}
NR==FNR{l[tolower($1)]=$0;cpt[tolower($1)]=0;next}
{
if(tolower($1) in cpt){
cpt[tolower($1)]++
}
}
END{
for(key in cpt){
print l[key],"total",cpt[key]
}
}
' F1.txt F2.txt
答案2
首先,我们将该F2.txt
文件视为 CSV 文件,并以点作为字段分隔符。我们可以将第一个字段转换为小写,然后计算第一个字段中不同值的数量。通过使用磨坊主下面的( ) 中,按照要求以 a 作为分隔符mlr
输出唯一的第一个字段值及其计数::
$ mlr --csv --ifs . --ofs : -N put '$1 = tolower($1)' then count -g 1 F2.txt
a:2
b:1
c:2
要过滤此结果并将其与 的内容合并F1.txt
,我们使用join
:
$ mlr --csv --ifs . --ofs : -N put '$1 = tolower($1)' then count -g 1 F2.txt | join -t : F1.txt -
a:erwer|ee:2
b:eeeee|eee:1
c:ewrew|erwe:2
如果您希望将字符串total:
插入到末尾的数字之前,您可以将其传递到一个额外的管道阶段,该阶段将其添加为sed 's/.*:/&total:/'
.
$ mlr --csv --ifs . --ofs : -N put '$1 = tolower($1)' then count -g 1 F2.txt | join -t : F1.txt - | sed 's/.*:/&total:/'
a:erwer|ee:total:2
b:eeeee|eee:total:1
c:ewrew|erwe:total:2
使用的变体awk
:
$ awk -F : 'FNR==NR { split($0,a,"."); count[tolower(a[1])]++; next } $1 in count { printf "%s:total:%s\n", $0, count[$1] }' F2.txt F1.txt
a:erwer|ee:total:2
b:eeeee|eee:total:1
c:ewrew|erwe:total:2
代码awk
:
BEGIN { FS = ":" }
FNR==NR {
split($0,a,".")
count[tolower(a[1])]++
next
}
$1 in count {
printf "%s:total:%s\n", $0, count[$1]
}
只是为了好玩:展示如何使用 Miller 的 Docker 映像来运行上面的第一个命令:
docker run --rm -i jauderho/miller:latest \
--csv --ifs . --ofs : -N \
put '$1 = tolower($1)' then count -g 1 <F2.txt
... 甚至...
alias mlr='docker run --rm -i jauderho/miller:latest'
mlr --csv --ifs . --ofs : -N put '$1 = tolower($1)' then count -g 1 <F2.txt
答案3
Perl 在这里很适合,尽管它可以在 awk 中完成,或者像 @terdon 所示的各种工具管道一样(但请注意该解决方案中有关子字符串的警告)。
这是一些示例输入:
文件1
a
b
c
d
文件2
a.T1
a.T2
b.T3
C.T7
c.T4
和示例运行:
% perl count.pl file1 file2
a:2
b:1
c:2
d:0
这是 Perl 中相对结构化的版本。它使用该语言的“magic readline”功能来代替更明确的文件处理。它没有错误处理,而是运行时自动提供的。它使用“slurp 模式”读取密钥文件,因此对于该文件,关于其大小与内存的警告可能是值得的,但令人怀疑的是您要查找的密钥文件是否会非常大。它确实逐行正确地循环,file2
而不将其全部拉入内存,以便可以任意大。它使用正则表达式匹配,因此在最后的“.”之前的任何内容。然后 'T' 则可以匹配 中的单个数字file1
。
#!perl
use warnings;
use strict;
sub get_keys {
local $/ = undef;
return { map { lc $_ => 0 } split /\n/, <> };
}
sub count {
my $m = shift;
while (<>) {
if ( m/(.*)\.T\d/ ) {
$m->{lc $1}++ if exists $m->{lc $1}
}
}
return $m;
}
sub output {
my $m = shift;
for my $k ( sort keys %$m ) {
printf "%s:%d\n", $k, $m->{$k};
}
return;
}
output count get_keys;
这是一个更短、更一次性的版本,仍然是 Perl 语言,具有相同的核心逻辑。
#!perl
use warnings;
use strict;
undef $/;
my %map = map { lc $_ => 0 } split /\n/, <>;
$/ = "\n";
while (<>) {
if ( m/(.*)\.T\d/ ) {
$map{lc $1}++ if exists $map{lc $1}
}
}
for my $k ( sort keys %map ) {
printf "%s:%d\n", $k, $map{$k};
}
如果您想要一个简短的命令行版本,它可以执行相同的操作,但仍然不太难读,这里是适应它的第二个版本。
% perl -e '
undef $/;
%m = map { lc $_ => 0 } split /\n/, <>;
$/ = "\n";
while (<>) { if ( /(.*)\.T\d/ ) { $m{lc $1}++ } }
for $k ( sort keys %m ) { printf "%s:%d\n", $k, $m{$k}; }
' file1 file2
a:2
b:1
c:2
d:0
或者使用像 awk 这样带有 BEGIN 和 END 块的输入自动循环:
% perl -ne '
BEGIN {
undef $/;
%m = map { lc $_ => 0 } split /\n/, <>;
$/ = "\n";
}
if ( /(.*)\.T\d/ ) { $m{lc $1}++ }
END {
for $k ( sort keys %m ) { printf "%s:%d\n", $k, $m{$k}; }
}
' file1 file2
a:2
b:1
c:2
d:0
请注意,此答案中的所有代码示例都是同一程序。它们都从相同的输入产生相同的输出。它们甚至没有被实质性重写。这是使用相同语言的相同逻辑,只需进行很小的调整。
您评估解决方案时应遵循的一个轴是您稍后返回该解决方案进行维护的容易程度。如果这是一个只做这一件事的简单工具,并且我需要重复运行它,但怀疑我永远不需要将代码扩展为其他东西,我可能会使用第二个版本或接近它的版本。如果这是一个更大的程序的一部分,我会从第一个程序开始,并可能添加更明确的文件处理,例如open
、close
和错误处理。