awk 动态字符串匹配

awk 动态字符串匹配

我有两个文件 - (a) 一个文件,我从中获取名称和该名称所在的文件;(b) 一个实际文件,我要在该文件上匹配名称并在其前后获取两个单词。

第一个文件的快照

Ito 65482.txt
David Juno Ilrcwrry Hold 73586.txt
David Jones 73586.txt
Jacob FleUchbautr 73586.txt

名称是一个用空格分隔的字符串,如上所述。

文件 65482.txt 的快照(其中包含乱码 OCR 文本)

nose just brnukiiitt tip tinwallfin the golden 
path of Ito etmlmbimiiit tlmmgli the trees 
Butt It as tie not intra and plcturosiiiicness 
limit wo were of m that is not altogether We 
and hunting and llslilng In plenty anti lit lIly 

所需的输出格式

Ito path of etmlmbimiiit tlmmgli 

即赛前赛后两个词。

#!/bin/bash
fPath='/Users/haimontidutta/Research/IIITD/Aayushee/Code/Source-Code/Thesis/src/ReputedPersonDetection/data/OutputofNERFinal_v1a.txt'
echo "Enter Script"

while IFS=' ' read -ra arr
do 
 fname="${arr[${#arr[@]}-1]}"
 #echo $fname
 name=""
 for((idx=0; idx<$[${#arr[@]}-1]; ++idx))
 do
  name=$name" ${arr[idx]}"
 done
 #echo $name 
 filepath='/Users/haimontidutta/Research/IIITD/Aayushee/Code/Source-Code/Thesis/src/ReputedPersonDetection/data/final/'$fname
 #echo $fName
 #echo $filepath

 #Extract window around name
 awk -v nm="$name" '{
     for(i=1;i<=NF;i++)
     {
       #print $i 
       if($i~$nm)
       {
        print nm OFS  $(i-2) OFS $(i-1) OFS $(i+1) OFS $(i+2); exit;
      }}}' $filepath
done < $fPath

我能够提取名称和文件路径,但在 awk 语句中,名称的动态匹配失败,并且无法获取窗口。

我该怎么做呢?

答案1

对数组的数组使用 GNU awk:

$ cat tst.awk
NR==FNR {
    file = $NF
    name = $1 (NF>2 ? " " $2 : "")
    if ( !(file in file2names) && ((getline line < file) > 0) ) {
        close(file)
        ARGV[ARGC++] = file
    }
    file2names[file][name]
    next
}
{
    $0 = " " $0 " "
    for (name in file2names[FILENAME]) {
        if ( pos = index($0," "name" ") ) {
            split(substr($0,1,pos),bef)
            split(substr($0,pos+length(name)+1),aft)
            print name, bef[1], bef[2], aft[1], aft[2]
        }
    }
}

$ awk -f tst.awk file
Ito path of etmlmbimiiit tlmmgli

如果您实际上希望“file”中的所有文件名前字符串成为名称的一部分,而不仅仅是前 1 或 2 个字符串(请参阅下面的注释),那么只需更改:

name = $1 (NF>2 ? " " $2 : "")

对此,gawk 表示:

name = gensub(/\s+\S+$/,"",1)

或者在任何 awk 中:

name = $0
sub(/ +[^ ]+$/,"",name)

对于任何其他 awk,您只需将文件的名称存储为空格分隔的字符串,例如,而不是file2names[file][name]您这样做file2names[file] = (file in file2names ? file2names[file] FS : "") name,然后在循环之前拆分它们,例如,而不是for (name in file2names[file])您这样做split(file2names[FILENAME],names); for (name in names)

上面的输入file只是示例中的第一个文件。

答案2

给定输入文件:

$ cat first.file
Ito 65482.txt
David Juno Ilrcwrry Hold 73586.txt
David Jones 73586.txt
Jacob FleUchbautr 73586.txt

$ cat 65482.txt
nose just brnukiiitt tip tinwallfin the golden
path of Ito etmlmbimiiit tlmmgli the trees
Butt It as tie not intra and plcturosiiiicness
limit wo were of m that is not altogether We
and hunting and llslilng In plenty anti lit lIly

$ cat 73586.txt
Lorem ipsum David Jones dolor sit amet, consectetur adipiscing elit. Curabitur non ultrices tellus. Donec porttitor sodales mattis. Nulla eu ante eget libero dictum accumsan nec non odio. Nullam lobortis porttitor mauris a feugiat. Vestibulum ultrices ipsum at maximus consequat. Vivamus molestie Jacob FleUchbautr tortor ac felis varius gravida. Cras accumsan dolor at velit sodales auctor. Vestibulum sit amet scelerisque eros, quis porta orci. Donec eget erat dolor. Integer id vestibulum massa. Quisque lacus risus, venenatis nec euismod nec, ultrices sed mi. Proin tincidunt ipsum mattis lectus pulvinar interdum. Suspendisse convallis justo iaculis, semper nisl at, imperdiet ante.
# ..........^^^^^^^^^^^..................................................................................................................................................................................................................................................................................^^^^^^^^^^^^^^^^^

然后:

mapfile -t files < <(awk '{print $NF}' first.file | sort -u)

word='[^[:blank:]]+'

for file in "${files[@]}"; do
    mapfile -t names < <(grep -wF "$file" first.file | sed -E 's/ [^ ]+$//')
    pattern="($word $word) ($(IFS='|'; echo "${names[*]}")) ($word $word)"
    declare -p file pattern
    grep -oE "$pattern" "$file" | sed -E "s/$pattern/\\2 \\1 \\3/"
done

输出

declare -- file="65482.txt"
declare -- pattern="([^[:blank:]]+ [^[:blank:]]+) (Ito) ([^[:blank:]]+ [^[:blank:]]+)"
Ito path of etmlmbimiiit tlmmgli
declare -- file="73586.txt"
declare -- pattern="([^[:blank:]]+ [^[:blank:]]+) (David Juno Ilrcwrry Hold|David Jones|Jacob FleUchbautr) ([^[:blank:]]+ [^[:blank:]]+)"
David Jones Lorem ipsum dolor sit
Jacob FleUchbautr Vivamus molestie tortor ac

那个正则表达式需要名字前后各出现 2 个单词。如果名称出现在行的开头或结尾,则不匹配。

答案3

这可以在 中完成awk,但在 IMO 中更容易做到perl。甚至在您考虑到有超过 800 个 perl 库模块用于各种自然语言处理任务之前。语言::*,这就是你似乎正在做的事情。

下面的 perl 脚本首先使用文件名作为哈希来构建一个常用的 perl 数据结构,称为数组哈希 (HoA)到关联数组(又名hash),并且每个键的价值观是名称的索引数组。man perldsc有关 HoA 和其他 Perl 数据结构的更多信息,请参阅 参考资料。

HoA%files最终会得到如下数据:

{
  "65482.txt" => ["Ito"],
  "73586.txt" => ["David Juno Ilrcwrry Hold", "David Jones", "Jacob FleUchbautr"],
}

它还使用一个名为 的数组@order来记住每个文件名出现的顺序,以便稍后可以按相同的顺序处理它们(这通常很有用,因为与许多其他语言一样,perl 哈希本质上是无序的。如果您不这样做不关心顺序,你可以只迭代哈希的键)

如果文件名不存在,它会向 STDERR 打印一条警告消息,并跳到“第一个”文件的下一行。如果您不想要警告,可以删除或注释掉该print STDERR ...行,或者在运行时将 stderr 重定向到 /dev/null 。

一旦完成%filesHoA 的构建,它就会打开每个文件进行读取,创建并预编译与该特定文件所需的任何名称相匹配的正则表达式,并打印与 RE 相匹配的每一行。

它构建的正则表达式最终会得到如下值:

(((\w+\s+){2})(David Juno Ilrcwrry Hold|David Jones|Jacob FleUchbautr)((\s+\w+){2}))

这样做的原因是只需要处理每个文件名一次,并且每个文件的每一行只需检查一次,看看它是否与其中一个名称匹配。如果您有许多文件和/或它们非常大,那么与重复读取和匹配每个文件的每一行(对于“第一个”文件中列出的每个名称一次)的简单方法相比,这会带来巨大的性能提升 - 例如,如果如果您有 1000 个文件,每个文件有 1000 行,总共需要匹配 50 个名称,那么简单的方法必须读取并匹配一行 5000 万次(文件 * 行 * 名称),而不是仅仅 100 万次(文件 * 行)

该脚本的设置可以让您轻松选择如何匹配匹配名称前后的单词。取消评论仅有的my $count=脚本中的两行之一。第一个严格要求每个名称之前和之后恰好有两个单词 - 这已经没有注释。第二个是宽松的关于名称之前或之后可以存在多少个单词(从 0 到 2)。

#!/usr/bin/perl -l

use strict;
my %files = ();
my @order = ();

# Un-comment only one of the following two lines.
my $count=2;
#my $count='0,2';

# First, build up a HoA where the key is the filename and
# the value is an array of names to match in that file.
while(<>) {
  s/^\s+|\s+$//;   # strip leading and trailing spaces
  next if (m/^$/); # skip empty lines
  my ($name,$filename) = m/^(.*)\s+(.*)$/; # extract name and filename

  # warn about and skip filenames that don't exist
  if (! -e $filename) {
    print STDERR "Error found on $ARGV, line $.: '$filename' does not exist.";
    next;
  };

  # remember the order we first see each filename.
  push @order, $filename unless ( defined($files{$filename}) );

  # Add the name to the %files HoA
  push @{ $files{$filename} }, $name;
};

# Process each file once only, in order.
foreach my $filename (@order) {
  open(my $fh,"<",$filename) || die "Error opening $filename for read: $!\n";

  my $re = "(((\\w+\\s+){$count})(" .           # two words
           join('|',@{ $files{$filename} }) .   # the names
           ")((\\s+\\w+){$count}))";            # and two words again

  $re = qr/$re/;  # add an 'i' after '/' for case-insensitive

  while(<$fh>) {
    if (m/$re/) {
      my $found = join(" ",$4,$2,$5);
      $found =~ s/\s\s+/ /g;
      print $found
    };
  };
}

另存为,例如match.pl并使用 使其可执行chmod +x match.pl,然后运行如下:

$ ./match.pl first.txt 
Error found on first.txt line 2: '73586.txt' does not exist.
Error found on first.txt line 3: '73586.txt' does not exist.
Error found on first.txt line 4: '73586.txt' does not exist.
Ito path of etmlmbimiiit tlmmgli

顺便说一句,这不是您所要求的,但我建议打印匹配的名称,并用冒号 ( :) 或除空格以外的任何内容与找到的单词分隔开。一个标签也很好。这将使使用其他程序解析输出文件变得更加容易。 IE

Ito:path of etmlmbimiiit tlmmgli

您可以通过将该行更改my $found =为:

my $found = "$4:" . join(" ",$2,$5);

或者

my $found = "$4\t" . join(" ",$2,$5);

相关内容