我有两个文件 - (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 。
一旦完成%files
HoA 的构建,它就会打开每个文件进行读取,创建并预编译与该特定文件所需的任何名称相匹配的正则表达式,并打印与 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);