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

처음 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}))

그 이유는 각각의 파일명만 처리하면 되기 때문이다.한 번, 각 파일의 각 줄은 이름 중 하나와 일치하는지 확인하기 위해 한 번만 확인하면 됩니다. 파일이 많거나 매우 큰 경우 각 파일의 각 줄을 반복적으로 읽고 일치시키는 간단한 접근 방식("첫 번째" 파일에 나열된 각 이름에 대해 한 번)보다 효율성이 떨어집니다. 엄청난 성능 향상을 제공합니다. - 예를 들어, 각각 1000개의 라인이 있는 1000개의 파일이 있고 총 50개의 이름을 일치시켜야 하는 경우 간단한 방법은 단순히 라인을 5천만 번(파일 * 라인 * 이름) 읽고 일치시켜야 합니다. 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

그런데, 이것은 귀하가 요청한 것이 아니지만, :찾은 단어와 일치하는 이름을 콜론( ) 또는 공백 이외의 다른 문자로 구분하여 인쇄하는 것이 좋습니다. 라벨도 있으면 좋을 것 같아요. 이렇게 하면 다른 프로그램을 사용하여 출력 파일을 구문 분석하는 것이 더 쉬워집니다. 즉

Ito:path of etmlmbimiiit tlmmgli

줄을 다음과 같이 변경하면 됩니다 my $found =.

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

또는

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

관련 정보