sed는 예외 목록으로 교체합니다.

sed는 예외 목록으로 교체합니다.

많은 대용량 텍스트 파일에서 문자열을 바꿔야 하는데 특이한 문자열(200개 이상의 항목) 목록이 있습니다. 예를 들어:

# I want to replace every "dank". Except when it comes in the following form:
 
dankine
dankzwd
nudankip
dankphys
danko.mod
... (The list is 200+ items long)

내 현재 정규식은 다음과 같습니다.

sed -e "s/dank/monk/g" /path/to/file

파일의 내용은 다음과 같습니다.

xdankine redankus
dankzwd 
danke dankbe
testdank

실행 후 파일 내용은 다음과 같습니다.

xmonkine remonkus
monkzwd 
monke monkbe
testmonk

그러나 나는 내용이 다음과 같기를 원합니다.

xdankine remonkus
dankzwd 
monke monkbe
testmonk

dankine과 dankzwd가 내 제외 목록에 있기 때문입니다.

파일의 각 줄에는 여러 개의 가능한 대체 항목이 포함될 수 있습니다.

어떻게 해야 하나요?

답변1

dank예제와 같이 각 줄이 한 번만 발생하는 경우 역방향 주소를 사용할 수 있습니다.

sed -E '/dankine|dankzwd|nudankip|dankphys|danko\.mod/!s/dank/monk/'

각 줄이 여러 번 나타날 수 있는 경우 파일의 일부가 될 수 없는 문자를 사용할 수 있습니다. 예를 들어 #모두 dank로 변경하고 #, 단어 목록을 다시 로 변경하고, 나머지는 다음으로 변경합니다 #.monk

sed 's/dank/#/g;s/#ine/dankine/g;s/#zwd/dankzwd/g;s/nu#ip/nudankip/g;s/#phys/dankphys/g;s/#o\.mod/danko.mod/g;s/#/monk/g'

(어떤 문자라도 나타날 수 있다면 대신 개행 문자를 사용하세요)

업데이트: 파일에서 제외 목록을 읽기 위한 새로운 요구 사항

블랙리스트를 파일에 작성exclusion.list 후행 개행 포함(스크립트는 이를 사용하여 첫 번째 파일이 끝나는 위치를 감지합니다):

sed -e '1,/^$/{H;d;}' -e 'G;s/\n/&&/;:loop' -e 's/\(.*da\)\(nk.*\)\(.*\n\1\2\n\)/\1#\2\3/;tloop' -e 's/\n.*//;s/dank/monk/g;s/da#nk/dank/g' exclusion.list file

또는 여러 줄을 읽는 것이 더 쉬울 수 있기 때문에

sed '1,/^$/{H;d;}
  G
  s/\n/&&/
  :loop
  s/\(.*da\)\(nk.*\)\(.*\n\1\2\n\)/\1#\2\3/
  tloop
  s/\n.*//
  s/dank/monk/g
  s/da#nk/dank/g' exclusion.list file

그럼에도 불구하고, 읽는 것보다 여전히 더 쉬울 것입니다. 이 개념은

  • 공간을 예약하려면 제외 목록을 읽어보세요.
  • 파일의 각 줄에 대해 이 목록을 예약된 공간에 추가하세요.
  • dankda#nk향후 대체를 방지하려면 목록에 있는 모든 파일을 다음으로 바꾸십시오.
  • 그런 다음 목록을 삭제하고 모두를 다음으로 바꾸고 마지막으로 danks 에서 monk제거합니다 .#da#nk

l작동 방식에 대한 설명은 끝에 추가됩니다 :loop.

문제를 지적해준 Stéphane에게 감사드립니다. dankfoodank이제 문제가 해결되었습니다. 그런데 사건의 요건은 아직 불분명한데, of 만 보호되기 때문 dankdank일까 , 아니면 두 번째 of가 일부로 보호되기 때문에 그대로 유지해야 하는 걸까 , 아니면 범위를 벗어나는 걸까.dankmonkdankdankdadankdankdadankdankda

답변2

모든 Unix 시스템의 모든 쉘에서 awk를 사용하고 리터럴 문자열 조작을 사용하므로 입력 또는 예외 목록의 정규식이나 역참조 메타 문자에 대해 신경 쓰지 않습니다.

$ cat tst.awk
NR==FNR {
    mask[$0] = RS NR RS
    next
}
{
    delete changed
    for (exception in mask) {
        while ( s=index($0,exception) ) {
            $0 = substr($0,1,s-1) mask[exception] substr($0,s+length(exception))
            changed[exception]
        }
    }

    gsub(/dank/,"monk")

    for (exception in changed) {
        while ( s=index($0,mask[exception]) ) {
            $0 = substr($0,1,s-1) exception substr($0,s+length(mask[exception]))
        }
    }

    print
}

$ awk -f tst.awk exceptions file
xdankine remonkus
dankzwd
monke monkbe
testmonk

dankfoo위의 내용은 예 를 들어 다른 예외의 하위 문자열인 예외가 없으며 dankdankfoo질문의 예에서 유사한 상황을 표시하지 않았기 때문에 가정합니다. 이렇게 하는 경우 긴 상위 문자열이 짧은 하위 문자열 앞에 오도록 예외 파일의 순서를 지정하고 첫 번째 루프에서 예외를 마스킹할 때 로 대체되지 않도록 입력 순서대로 반복합니다 xdankdankfooy.xdank<replacement>yx<replacement>y

답변3

제외 목록은 200개 이상일 수 있으므로 정규식에 과부하가 걸리지 않도록 먼저 제외 목록 파일을 사용하여 sed 코드를 생성하고 생성된 코드를 데이터 입력에 적용합니다.

GNU sed

sed -e '
  1i\
s/dank/\\n/g
  h;s:[\&/]:\\&:g
  x;s/dank/\n/g
  s:[][^$\/.*]:\\&:g
  s/\n/\\n/g;G
  s:\n:/:;s:.*:s/&/g:
  $a\
s/\\n/MONK/g
' excludes.txt | sed -f - file

산출:-

xdankine reMONKus
dankzwd
MONKe MONKbe
testMONK

개념의 증거:-

  • 먼저 모든 dank를 리터럴 개행 문자로 변환하여 해당 문자가 발견되지 않도록 합니다.
  • 그런 다음 제외 목록의 한 행을 nudankip아래와 같이 바꾸십시오. 제외 목록의 모든 행에 대해서도 동일합니다.
  • s/nu\nip/nudankip/g
  • 문제는 sed s/// 표현식의 rhs 및 lhs 제외 목록을 이스케이프해야 한다는 것입니다.

답변4

이를 통해 perl다음을 수행할 수 있습니다.

perl -pe '
  BEGIN{
    chomp (@excl = <STDIN>);
    $re = "(" . join( "|", map {qr{\Q$_\E}} @excl) . ")|dank"
  }
  s{$re}{$1//"monk"}ge' input < exclusion.list

이는 다음과 같은 정규식을 구성합니다.

(dankine|dankzwd|nudankip|dankphys|danko\.mod)|dank

$1발생하는 모든 항목을 if is set(제외 항목이 일치함) 또는 else(제외 항목이 일치함) (따라서 일치하는 항목이므로 기본적으로 아무 작업도 수행하지 않음) 로 바꿉니다 .$1monkdank

제외 항목에 dankzwd및 가 모두 포함된 경우 먼저 ( ) 로 대체된 다음 그것으로만 대체되기 때문에 zwddank여전히 제외 항목이 됩니다 .dankzwddankdankzwdmonkdankzwddankzwd$1dank

한 가지 해결 방법은 제외가 발생하는 마스크 문자열의 모든 위치를 기록한 다음 교체를 수행할 때 dank마스크에 괜찮다고 표시된 부분만 교체를 수행하는 것입니다.

perl -spe '
  BEGIN {
    chomp (@excl = <STDIN>);
    $word_len = length $word;
  }
  my $len = length;
  my $mask = "-" x $len;
  my $i;
  for my $e (@excl) {
    my $e_len = length $e;
    my $hide = "#" x $e_len;
    for (my $o = 0;
         $o < $len && ($i = index($_, $e, $o)) >= 0;
         $o = $i + 1) {
      substr($mask, $i, $e_len) = $hide;
    }
  }
  s{dank}{substr($mask, pos, $word_len) =~ /-/ ? $repl : $&}ge
  ' -- -word=dank -repl=monk input < exclusion.list

예를 들어, 입력 줄에 다음이 포함되어 있는 경우:

dodankdankdankoodankdodank

제외: dankdank, dankdo마스크는 단계별로 구축됩니다.

 --------------------------
 --########---------------- # first dankdank
 --############------------ # second dankdank
 --############--######---- # first and only dankdo
 dodankdankdankoodankdodank
                       ^^^^

dank그러면 제외의 영향을 받지 않는 마지막 부분 만 남습니다 .

관련 정보