2つのファイルがあります。 (a)名前をインポートするファイルと名前を含むファイル、および(b)名前を一致させ、その前後の2つの単語をインポートしたい実際のファイル。
最初のファイルのスナップショット
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
つまり、ゲームの前とゲーム後の2つの単語です。
#!/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
最初の1つまたは2つ(以下の説明を参照)だけでなく、「file」のすべてのファイル名以前の文字列を名前の一部として含めるには、次のように変更します。
name = $1 (NF>2 ? " " $2 : "")
これに対してゴークはこう述べた。
name = gensub(/\s+\S+$/,"",1)
またはいずれにせよ:
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スクリプトは、まずファイル名をハッシュとして使用して、HoA(ハッシュ配列)と呼ばれる一般的なPerlデータ構造を構築します。鍵連想配列(別名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にリダイレクトできます。
HoAのビルドが完了したら、各%files
ファイルを読み取って開き、特定のファイルに必要な名前と一致する正規表現を作成してプリコンパイルし、REに一致する各行を印刷します。
それが構築する正規表現は次の値で終わります。
(((\w+\s+){2})(David Juno Ilrcwrry Hold|David Jones|Jacob FleUchbautr)((\s+\w+){2}))
その理由は、それぞれのファイル名だけを処理すればよいからだ。一度、各ファイルの各行は、名前の1つと一致することを確認するために一度だけチェックする必要があります。ファイルが多いか非常に大きい場合、各ファイルの各行を繰り返し読み込み、一致させる簡単なアプローチ(「最初の」ファイルにリストされている各名前に対して1回)よりも効率が悪くなります。膨大なパフォーマンス向上を提供します。 - たとえば、それぞれ1000行の1000個のファイルがあり、合計50個の名前を一致させる必要がある場合、簡単な方法は単に5000万回(ファイル*行*名)を読み取って一致させる必要があります。 100万回(ファイル・ライン)
一致する名前の前後の単語を一致させる方法を簡単に選択できるようにスクリプトが設定されています。コメントをキャンセルただmy $count=
スクリプトの2行のうちの1つです。最初は、それぞれの名前の前に正確に2つの単語が来なければならないと厳密に要求します。これはすでにコメントされていません。 2番目は、名前の前後にどのくらいの単語が存在するか(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
しかし、これはあなたが要求したものではありませんが、見つかった:
単語と一致する名前をコロン()またはスペース以外の文字で区切って印刷することをお勧めします。ラベルもあればいいと思います。これにより、他のプログラムを使用して出力ファイルを解析するのが簡単になります。つまり
Ito:path of etmlmbimiiit tlmmgli
行を次のように変更するだけですmy $found =
。
my $found = "$4:" . join(" ",$2,$5);
または
my $found = "$4\t" . join(" ",$2,$5);