많은 대용량 텍스트 파일에서 문자열을 바꿔야 하는데 특이한 문자열(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
그럼에도 불구하고, 읽는 것보다 여전히 더 쉬울 것입니다. 이 개념은
- 공간을 예약하려면 제외 목록을 읽어보세요.
- 파일의 각 줄에 대해 이 목록을 예약된 공간에 추가하세요.
dank
da#nk
향후 대체를 방지하려면 목록에 있는 모든 파일을 다음으로 바꾸십시오.- 그런 다음 목록을 삭제하고 모두를 다음으로 바꾸고 마지막으로
dank
s 에서monk
제거합니다 .#
da#nk
l
작동 방식에 대한 설명은 끝에 추가됩니다 :loop
.
문제를 지적해준 Stéphane에게 감사드립니다. dankfoodank
이제 문제가 해결되었습니다. 그런데 사건의 요건은 아직 불분명한데, of 만 보호되기 때문 dankdank
일까 , 아니면 두 번째 of가 일부로 보호되기 때문에 그대로 유지해야 하는 걸까 , 아니면 범위를 벗어나는 걸까.dankmonk
dank
dankda
dankdank
da
dank
dankda
답변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>y
x<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(제외 항목이 일치함) (따라서 일치하는 항목이므로 기본적으로 아무 작업도 수행하지 않음) 로 바꿉니다 .$1
monk
dank
제외 항목에 dankzwd
및 가 모두 포함된 경우 먼저 ( ) 로 대체된 다음 그것으로만 대체되기 때문에 zwddank
여전히 제외 항목이 됩니다 .dankzwddank
dankzwdmonk
dankzwd
dankzwd
$1
dank
한 가지 해결 방법은 제외가 발생하는 마스크 문자열의 모든 위치를 기록한 다음 교체를 수행할 때 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
그러면 제외의 영향을 받지 않는 마지막 부분 만 남습니다 .