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

无论如何,这可能仍然比阅读更容易。这个概念是

  • 读取排除列表以保留空间
  • 对于文件的每一行,将该列表附加到保留空间中
  • dank将列表中出现的文件中的每个替换为da#nk,以防止以后被替换
  • 然后删除列表,全部替换dank为,最后从s 中monk删除。#da#nk

添加l在后面:loop说明了工作原理。

感谢 Stéphane 提示dankfoodank问题,现已解决。然而,对 case 的要求dankdank仍不清楚,是dankmonk因为只有dankofdankda受到保护,还是应该保留,dankdank因为da第二个的 ofdank作为 的一部分受到保护,dankda还是超出了范围?

答案2

在每个 Unix 机器上的任何 shell 中使用任何 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

上面假设您没有作为其他异常的子字符串的异常,例如dankfoodankdankfoo因为您没有在问题的示例中显示类似的情况。如果这样做,请确保对异常文件进行排序,以便较长的超字符串出现在较短的子字符串之前,并按照输入顺序对其进行迭代,这样在第一个循环中屏蔽异常时就不会替换为xdankdankfooyxdank<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

概念证明:-

  • 首先,将所有 danks 转换为字面换行符,保证找不到一个字符。
  • 然后将排除列表中的一行转为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(所以匹配了什么,所以基本上什么也不做)。$1monkdank

请注意,如果排除项同时包含dankzwdzwddank,则 仍然会变成dankzwddankdankzwdmonk因为它先dankzwddankzwd( $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

并且排除有:dankdankdankdo掩码将逐步构建

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

那么只剩下dank最后不受排除影响的部分。

相关内容