awk 動的文字列の一致

awk 動的文字列の一致

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

これはで行うことができますが、awkIMOではで行う方が簡単ですperl。さまざまな自然言語処理を行うための800以上のPerlライブラリモジュールがあることを考慮する前に言います。言語::*、あなたがやっているようです。

次のPerlスクリプトは、まずファイル名をハッシュとして使用して、HoA(ハッシュ配列)と呼ばれる一般的なPerlデータ構造を構築します。連想配列(別名hash)で、各キーについて価値インデックス付きの名前の配列です。man perldscHoAやその他の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);

関連情報