문자열 목록과 해당 대체 목록을 기반으로 파일의 문자열 바꾸기

문자열 목록과 해당 대체 목록을 기반으로 파일의 문자열 바꾸기

다음에서 문자열을 바꾸려고 합니다 file A.

Hello Peter, how is your dad? where is mom? 

교체할 문자열은 다음 위치에 있습니다 file B.

Peter
dad
mom

해당 대체 항목은 다음 위치에 있습니다 file C.

John
wife
grandpa

예상되는 결과:

Hello John, how is your wife? where is grandpa?

의 값을 의 해당 행 값으로 편집하고 바꿀 수 있습니까 file A?file Bfile C

지금까지 내가 한 일:

 cat 1.txt | sed -e "s/$(sed 's:/:\\/:g' 2.txt)/$(sed 's:/:\\/:g' 3.txt)/" > 4.txt

file B& 에 한 줄만 있으면 유효하고 file C, 두 줄 이상 있으면 유효하지 않습니다.

답변1

가장 간단한 방법 sed은 이 두 목록을 처리하여 하나로 바꾸는 것입니다.스크립트 파일예를 들어

s/line1-from-fileB/line1-from-fileC/g
s/line2-from-fileB/line2-from-fileC/g
....................................
s/lineN-from-fileB/lineN-from-fileC/g

그런 다음 sed실행하고 fileA편집합니다.적절한접근 방식은 먼저 LHS/를 처리 RHS하고 해당 줄에 나타날 수 있는 특수 문자를 이스케이프한 다음 , 구분 기호 및 (예: with )를 결합 LHSRHS추가 하고 결과를 다음으로 파이프하는 것입니다 .s/gpastesed

paste -ds///g /dev/null /dev/null \
<(sed 's|[[\.*^$/]|\\&|g' fileB) <(sed 's|[\&/]|\\&|g' fileC) \
/dev/null /dev/null | sed -f - fileA

따라서 one paste과 three는 sed줄 수에 관계없이 각 파일을 한 번만 처리합니다.
이는 쉘이 프로세스 대체를 지원하고 다음을 sed읽을 수 있다고 가정합니다.스크립트 파일~에서표준 입력. 또한, 그 자리에서 편집이 되지 않습니다. ( -i모든 sed버전이 지원하는 것은 아니기 때문에 스위치는 생략했습니다 .)

답변2

대체를 서로 독립적으로 수행하려면 다음과 같이 하십시오.

foo -> bar
bar -> foo

에 적용

foobar

결과적으로:

barfoo

foofoo순진한 번역 과는 달리 s/foo/bar/g; s/bar/foo/g다음과 같이 할 수 있습니다.

perl -pe '
  BEGIN{
    open STRINGS, "<", shift@ARGV or die"STRINGS: $!";
    open REPLACEMENTS, "<", shift@ARGV or die "REPLACEMENTS: $!";
    while (defined($a=<STRINGS>) and defined($b=<REPLACEMENTS>)) {
      chomp ($a, $b);
      push @repl, $b;
      push @re, "$a(?{\$repl=\$repl[" . $i++. "]})"
    }
    eval q($re = qr{) . join("|", @re) . "}";
  }
  s/$re/$repl/g' strings.txt replacements.txt fileA 

이는 perl에서 예상되는 정규식 입니다 patterns.txt. Perl 정규식은 임의의 코드를 실행할 수 있으므로 이를 삭제하는 것이 중요합니다. 고정 문자열만 바꾸려면 다음과 같이 변경할 수 있습니다.

perl -pe '
  BEGIN{
    open PATTERNS, "<", shift@ARGV or die"PATTERNS: $!";
    open REPLACEMENTS, "<", shift@ARGV or die "REPLACEMENTS: $!";
    for ($i = 0; defined($a=<PATTERNS>) and defined($b=<REPLACEMENTS>); $i++) {
      chomp ($a, $b);
      push @string, $a;
      push @repl, $b;
      push @re, "\\Q\$string[$i]\\E(?{\$repl=\$repl[$i]})"
    }
    eval q($re = qr{) . join("|", @re) . "}";
  }
  s/$re/$repl/g' patterns.txt replacements.txt fileA 

답변3

각 대상 단어가 파일에서 한 번만 발생한다는 것을 보여주는 이 간단한 예에서는 간단히 다음을 수행할 수 있습니다.

$ paste fileB fileC | while read a b; do sed -i "s/$a/$b/" fileA; done
$ cat fileA
Hello John, how is your wife? where is grandpa? 

paste명령은 결합된 두 파일의 데이터를 인쇄합니다.

$ paste fileB fileC
Peter   John
dad wife
mom grandpa

while read각 행을 반복하는 간단한 루프를 통해 fileBas $afileCas 의 값을 저장합니다 $b. 그런 다음 명령은 sed첫 번째 항목을 . 이것을 세 번 반복하십시오.$a$b

대상 단어가 파일에 한 번만 나타난다는 것을 알고 있고(그렇지 않으면 대체해야 하는 단어를 결정하는 데 사용할 수 있는 추가 세부 정보를 제공해야 함) 파일이 작은 경우 이 접근 방식은 좋습니다. , 당신이 시연했듯이. 더 큰 파일의 경우 각 단어 쌍에 대해 한 번 실행해야 하므로 시간이 오래 걸리고 매우 비효율적입니다.

따라서 더 큰 파일이 있는 경우 다음과 같은 것을 원할 수 있습니다.

paste fileB fileC | 
    perl -lane '$words{$F[0]}=$F[1]} 
        END{open(A,"fileA"); while(<A>){s/$_/$words{$_}/ for keys %words; print}'

답변4

제가 만든 솔루션은 그다지 짧지는 않지만 충분히 간단하고 읽기 쉽습니다. 당신의 임무가 sed로 모든 일을 하는 것이 아니라면...?

 #!/usr/bin/bash

 cp A.txt D.txt

 x=1
 length=$(wc -l B.txt | sed 's/\ .*//g')

 until [ $x -eq $length ]; do

    Bx=$(awk "NR==$x" B.txt)
    Cx=$(awk "NR==$x" C.txt)

    sed -i "s/$Bx/$Cx/g" D.txt

    x=$(($x+1))

 done

 rm -f ./sed*

B.txt가 C.txt보다 길거나 그 반대의 경우 이 스크립트는 많은 정크 파일을 생성한다는 점에 유의하세요(아직 테스트하지는 않았습니다).

관련 정보