다른 파일의 패턴과 일치하는 파일의 여러 줄에 있는 여러 하위 문자열을 바꾸는 방법은 무엇입니까?

다른 파일의 패턴과 일치하는 파일의 여러 줄에 있는 여러 하위 문자열을 바꾸는 방법은 무엇입니까?

여러 IP 주소와 호스트 이름이 포함된 파일과 한 줄에 여러 IP 주소가 있는 일부 폴더가 포함된 다른 파일이 있습니다.

IP_호스트 이름.txt

host1 10.1.1.1
host2 10.2.2.2
host3 10.3.3.3
host100 10.50.50.50

path_ips.txt

/path1/foo/bar 10.1.1.1 10.2.2.2 10.3.3.3
/path2/foo/bar 10.3.3.3 10.7.7.7
/path3/foo/bar 10.4.4.4 10.8.8.8 10.29.29.29 10.75.75.75
/path100/foo/bar 10.60.60.60

IP 주소를 변경하고 싶어요path_ips.txt호스트 이름이 나오는 파일IP_호스트 이름.txt각 IP 주소와 일치하는 파일입니다.

원하는 출력path_ips.txt

/path1/foo/bar host1 host2 host3
/path2/foo/bar host3 host7
/path3/foo/bar host4 host8 host29 host75
/path100/foo/bar host60

나는 중첩된 sed를 사용하여 이 작업을 시도했습니다.읽는 동안루프는 다음과 같습니다:

#!/bin/sh

while read -r line
do
IP=$(echo $line| awk '{print $1}')
HN=$(echo $line| awk '{print $2}')

        while read -r line2
        do
               sed -i "s/$IP/$HN/g" path_ips.txt
        echo $line2 #to see the progress
        done < path_ips.txt

done < ip_hostname.txt

IP 주소와 호스트 이름 목록이 그다지 크지 않을 때 처음에는 잘 작동하지만 더 큰 목록을 사용하려고 하면 잘 작동합니다.IP_호스트 이름.txt파일을 열면 이상하게 동작하고 결과가 예상과 다릅니다. 말할 필요도 없이 완료하는 데 오랜 시간이 걸립니다.

이를 수행하는 더 좋고 효율적인 방법이 있습니까?

답변1

스크립트의 문제점은 sed일치하는 각 IP 주소에 대해 별도의 명령을 실행하므로 파일이 클 때 스크립트 속도가 매우 느려진다는 것입니다.

또한 중첩 루프가 있으므로 O(N*M)알고리즘에 시간 복잡도가 있습니다.

더 나은 접근 방식은 대체 수행을 사용하는 것입니다. awk이 방법을 사용하면 한 번에 모든 대체를 수행할 수 있습니다.

$ awk 'NR==FNR{h[$2]=$1;next}{for (i=2;i<=NF;i++) if ($i in h) $i = h[$i]}1' ip_hostname.txt path_ips.txt 
/path1/foo/bar host1 host2 host3
/path2/foo/bar host3 host7
/path3/foo/bar host4 host8 host29 host75
...
/path100/foo/bar host60

또는 더 읽기 쉬운 형식으로

awk '
    NR == FNR {
      h[$2] = $1
      next
    }
    {
      for (i=2; i<= NF; i++)
        if ($i in h)
          $i = h[$i]
    }
    1
' ip_hostname.txt path_ips.txt

이것은 파일 크기 O((N+M)lon(N))와 파일 크기가 복잡해야 합니다 . 제대로 작동하려면 메모리에 들어갈 수 있어야 하지만 최신 컴퓨터에서는 크기가 몇 GB가 아니면 문제가 되지 않습니다.Nip_hostname.txtMpath_ips.txtip_hostname.txt

답변2

이 작업은 sed에서 완전히 수행할 수 있지만 일반적으로 awk 답변이 더 읽기 쉽습니다.

#file toggle
1{x;s:^$:<IPs>:;x}
/^EOF$/{x;s:<IPs>:<paths>:;x;d}

#store hostname file
x;/<IPs>/{x;H;d}

#process path file
x;s: :>&:;s:$: :;G
:loop
    s:>( [^ ]+)( .*<paths>.*)\n([^ ]+)\1: \3>\2\n\3\1:
tloop
s:> .*::p

예제에 표시된 대로 코드에서는 공백을 파일 구분 기호로 가정합니다. 즉, 공백이 포함된 경로가 있으면 답이 틀릴 가능성이 높습니다.

이는 GNU sed를 사용하여 테스트되었지만 다른 sed 버전이 있을 수 있습니다. 그래도 문제가 해결되지 않으면 알려 주시기 바랍니다.

달리기:

sed -nrf SCRIPT_FILE ip_hostname.txt <(echo EOF) path_ips.txt > output.txt

참고: <(echo EOF)첫 번째 입력 파일이 끝나는 시점을 스크립트에 알리는 데 사용됩니다.

답변3

POSIX awk를 사용하십시오.

$ cat tst.awk
NR==FNR {
    map[$2] = $1
    next
}
match($0,/([[:space:]]+([0-9]{1,3}\.){3}[0-9]{1,3})+$/) {
    path = substr($0,1,RSTART-1)
    $0 = substr($0,RSTART,RLENGTH)
    for ( i=1; i<=NF; i++ ) {
        $i = ($i in map ? map[$i] : $i)
    }
    $0 = path OFS $0
}
{ print }

$ awk -f tst.awk ip_hostname.txt path_ips.txt
/path1/foo/bar host1 host2 host3
/path3/foo/bar 10.4.4.4 10.8.8.8 10.29.29.29 10.75.75.75
/path100/foo/bar 10.60.60.60

이는 경로에 공백이 포함된 경우에도 작동합니다. 단, 경로의 파일 이름 부분이 공백으로 끝나고 그 뒤에 IP 주소처럼 보이는 문자열이 올 수 있는 경우는 예외입니다. 예를 들어 IP 주소 대신 파일 이름의 일부인 /path/foo/bar 1.1.1.1경우 . 이런 일이 발생하면 ip_hostname.txt에서 다른 형식을 사용하여 경로와 IP 주소를 구분해야 합니다.1.1.1.1bar 1.1.1.1

답변4

다음 Perl 스크립트는 첫 번째 입력 파일( ip_hostname.txt)을 읽고 %IPs라는 연관 배열(해시)을 구축합니다. 여기서 키는 IP 주소이고 값은 호스트 이름입니다.

성능 최적화를 위해 해시의 각 키는 실제로 %IPs단어 경계 표시( ) 및 이스케이프된 메타 문자( & )가 포함된 IP 주소( )의 미리 컴파일된 정규식이므로 문자가 아닌 리터럴을 의미합니다.qr//\b\Q\E..

ip_hostname.txt정규식을 미리 컴파일하면 path_ips.txt의 줄당 IP 주소당 1(즉, 줄 수 x 의 줄 수 path_ips.txt)에서 IP 주소당 1개 까지 정규식을 컴파일하는 데 소요되는 CPU 시간이 최소화됩니다 . 파일 중 하나 또는 둘 다 큰 경우 성능에 상당한 영향을 미칩니다.

변수는 $first스크립트가 첫 번째 파일을 읽고 있는지 추적하는 데 사용됩니다. 메인 루프 이전에는 true(1)로 초기화되고 while각 입력 파일의 끝에서는 false(0)로 설정됩니다.

첫 번째 파일을 처리한 후 두 번째 파일( )의 각 줄에 대해 해시를 path_ips.txt반복하여 각 IP 주소를 검색하고 이를 연결된 호스트 이름으로 바꿉니다. %IPs그런 다음 (수정될 수 있는) 입력 행을 인쇄합니다.

각 줄에서 일치하는 IP 주소만 변경하고 나머지(공백 포함)는 그대로 둡니다.

#!/usr/bin/perl

use strict;

my %IPs;
my $first = 1;

while(<>) {
  if ($first) {
    chomp;                   # strip \n or \r\n line-endings
    my ($host,$ip) = split;  # assumes whitespace delimited input
    $IPs{qr/\b\Q$ip\E\b/} = $host;

  } else {
    foreach my $ip (keys %IPs) {
      s/$ip/$IPs{$ip}/g;
    };
    print;
  };
  $first = 0 if eof;
};

#use Data::Dump qw(dd);
#dd \%IPs;

예를 들어 다른 이름으로 저장 map-hostnames.pl하고 chmod +x.

출력 예( ip_hostname.txt질문에 언급된 모든 IP/호스트에 대한 매핑을 포함하도록 파일 편집):

$ ./map-hostnames.pl ip_hostname.txt path_ips.txt 
/path1/foo/bar host1 host2 host3
/path2/foo/bar host3 host7
/path3/foo/bar host4 host8 host29 host75
/path100/foo/bar host60

그런데, 해시가 어떻게 생겼는지 보려면 %IPs스크립트의 마지막 두 줄의 주석 처리를 해제하세요(필수).데이터::덤프모듈을 설치해야 함). 다음과 같이 보이지만 ip_hostname.txt실제 파일의 내용이 포함되어 있습니다.

{
  "(?^:\\b10\\.1\\.1\\.1\\b)"    => "host1",
  "(?^:\\b10\\.29\\.29\\.29\\b)" => "host29",
  "(?^:\\b10\\.2\\.2\\.2\\b)"    => "host2",
  "(?^:\\b10\\.3\\.3\\.3\\b)"    => "host3",
  "(?^:\\b10\\.4\\.4\\.4\\b)"    => "host4",
  "(?^:\\b10\\.50\\.50\\.50\\b)" => "host100",
  "(?^:\\b10\\.60\\.60\\.60\\b)" => "host60",
  "(?^:\\b10\\.75\\.75\\.75\\b)" => "host75",
  "(?^:\\b10\\.7\\.7\\.7\\b)"    => "host7",
  "(?^:\\b10\\.8\\.8\\.8\\b)"    => "host8",
}

관련 정보