用于替换2种已知模式之间的可变长度字符串

时间:2015-03-20 19:43:39

标签: sed

我希望能够在两种已知模式之间替换字符串。问题是,我想用一个长度相同的字符串替换它,只有' x'。

我们说我有一个包含以下内容的文件:

Hello.StringToBeReplaced.SecondString
Hello.ShortString.SecondString

我希望输出如下:

Hello.xxxxxxxxxxxxxxxxxx.SecondString
Hello.xxxxxxxxxxx.SecondString

5 个答案:

答案 0 :(得分:4)

使用sed循环

您可以使用sed,但所需的思维并不完全明显:

sed ':a;s/^\(Hello\.x*\)[^x]\(.*\.SecondString\)/\1x\2/;t a'

这是针对GNU sed; BSD(Mac OS X)sed和其他版本可能比较繁琐,需要:

sed -e ':a' -e 's/^\(Hello\.x*\)[^x]\(.*\.SecondString\)/\1x\2/' -e 't a'

两者的逻辑相同:

  • 创建标签a
  • 替换引导字符串和一系列x(捕获1),然后是非x,以及任意其他数据加上第二个字​​符串(捕获2),并将其替换为捕获1的内容,x和捕获2的内容。
  • 如果s///命令发生了变化,请返回标签a

当两个标记字符串之间没有非x时,它会停止替换。

对正则表达式的两次调整允许代码在一行上识别模式的两个副本。丢失将匹配锚定到该行开头的^,并将.*更改为[^.]*(以便正则表达式不那么贪婪):

$ echo Hello.StringToBeReplaced.SecondString Hello.StringToBeReplaced.SecondString |
> sed ':a;s/\(Hello\.x*\)[^x]\([^.]*\.SecondString\)/\1x\2/;t a'
Hello.xxxxxxxxxxxxxxxxxx.SecondString Hello.xxxxxxxxxxxxxxxxxx.SecondString
$

使用保留空间

hek2mgl建议sed使用保留空间的替代方法。这可以使用以下方式实现:

$ echo Hello.StringToBeReplaced.SecondString |
> sed 's/^\(Hello\.\)\([^.]\{1,\}\)\(\.SecondString\)/\1@\3@@\2/
>      h
>      s/.*@@//
>      s/./x/g
>      G
>      s/\(x*\)\n\([^@]*\)@\([^@]*\)@@.*/\2\1\3/
>      '
Hello.xxxxxxxxxxxxxxxxxx.SecondString
$

这个脚本不像循环版本那样健壮,但是当每一行与前中尾模式匹配时,写入正常。它首先将线分为三个部分:第一个标记,要修复的位和第二个标记。它重组了这两个标记,以便@分隔两个标记,然后是@@,并且要修改这个位。 h将结果复制到保留空间。删除所有内容,包括@@;用x替换要被修改的位中的每个字符,然后在模式空间中的x之后复制保留空间中的材料,并用换行符分隔它们。最后,识别并捕获x,主要标记和尾部标记,忽略换行符,@@@加上尾随材料,然后重新组合为导联标记,x&和39,以及尾部标记。

为了使其健壮,您可以识别该模式,然后将{}中显示的命令分组以对它们进行分组,以便它们仅在识别出模式时执行:

sed '/^\(Hello\.\)\([^.]\{1,\}\)\(\.SecondString\)/{
     s/^\(Hello\.\)\([^.]\{1,\}\)\(\.SecondString\)/\1@\3@@\2/
     h
     s/.*@@//
     s/./x/g
     G
     s/\(x*\)\n\([^@]*\)@\([^@]*\)@@.*/\2\1\3/
     }'

根据您的需求进行调整......

根据您的需求进行调整

  

[我试过你的一个解决方案,它工作正常。]   但是,当我尝试更换“你好”时通过我真实的字符串(这是   ' 1.2.840.')和我的第二个字符串(只是一个点' .'),停止了一切   工作。我想所有这些点都会混淆sed命令。   我试图实现的是改变这个' 1.2.840.10008.'至   ' 1.2.840.xxxxx.'

     

这个模式在我的文件中多次出现,变量号   在' 1.2.840.'之间要替换的字符和下一个点' .'

有时候让你的问题足够接近真实场景很重要 - 这可能就是这样。 Dot是一个元字符 sed正则表达式(以及正则表达式的大多数其他方言 - shell globbing是明显的例外)。如果'位被破坏'总是数字,然后我们可以收紧正则表达式,但实际上(当我查看前面的代码时),紧缩确实不会对限制措施造成太大影响。

使用正则表达式的任何解决方案都是一种平衡行为,必须提供方便性和缩写,以防止可靠性和精确性。

修改代码加数据

cat <<EOF |
transform this '1.2.840.10008.' to '1.2.840.xxxxx.'
OK, and hence 1.2.840.21. and 1.2.840.20992. should lose the 21 and 20992.
EOF

sed ':a;s/\(1\.2\.840\.x*\)[^x.]\([^.]*\.\)/\1x\2/;t a'

示例输出:

transform this '1.2.840.xxxxx.' to '1.2.840.xxxxx.'
OK, and hence 1.2.840.xx. and 1.2.840.xxxxx. should lose the 21 and 20992.

脚本中的更改为:

sed ':a;s/\(1\.2\.840\.x*\)[^x.]\([^.]*\.\)/\1x\2/;t a'
  1. 添加1\.2\.840\.作为开始模式。
  2. 修改&#39;字符以替换&#39;表达不是x.&#39;。
  3. 仅使用\.作为尾部模式。
  4. 如果您确定只想匹配数字,则可以将[^x.]替换为[0-9],在这种情况下,您不必担心空格,如下所述。< / p>

    您可能决定不希望空格匹配,以便随意评论如下:

    The net prefix is 1.2.840. And there are other prefixes too.
    

    不会以:

    结束
    The net prefix is 1.2.840.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.
    

    在这种情况下,您可能需要使用:

    sed ':a;s/\(1\.2\.840\.x*\)[^x. ]\([^ .]*\.\)/\1x\2/;t a'
    

    所以这些变化会持续下去,直到你得到足够精确的东西来做你想做的事情,而不做任何你不想要的当前数据集。编写防弹正则表达式需要精确指定您想要匹配的内容,并且可能非常困难。

答案 1 :(得分:2)

我选择perl:

perl -pe 's/(?<=Hello\.)(.*?)(?=\.SecondString)/ "x" x length($1) /e' file

答案 2 :(得分:1)

awk应该:

awk -F. '{for (i=1;i<=length($2);i++) a=a"x";$2=a;a=""}1' OFS="." file
Hello.xxxxxxxxxxxxxxxxxx.SecondString
Hello.xxxxxxxxxxx.SecondString

答案 3 :(得分:1)

Bash的工作原理

尽管perlsedawk解决方案可能是更好的选择,但Bash解决方案并不困难(只是更长)。 Bash也具有良好的逐字符处理能力:

#!/bin/bash

rep=0    # replace flag
skip=0   # delay reset flag

while read -r line; do                 # read each line

    for ((i=0; i<${#line}; i++)); do   # for each character in the line

        # if '.' and replace on, turn off and set skip
        [ ${line:i:1} == '.' -a $rep -eq 1 ] && { rep=0; skip=1; }

        # print char or "x" depending on replace flag
        [ $rep -eq 0 ] && printf "%c" ${line:i:1} || printf "x"

        # if '.' and replace off
        if [ ${line:i:1} == '.' -a $rep -eq 0 ]; then
            # if skip, turn skip off, else set replace on
            [ $skip -eq 1 ] && skip=0 || rep=1
        fi

    done

    printf "\n"

done

exit 0

<强>输入

$ cat dat/replacefile.txt
Hello.StringToBeReplaced.SecondString
Hello.ShortString.SecondString

<强>输出

$ bash replacedot.sh < dat/replacefile.txt
Hello.xxxxxxxxxxxxxxxxxx.SecondString
Hello.xxxxxxxxxxx.SecondString

答案 4 :(得分:1)

为了您的理智,请使用awk:

$ awk 'BEGIN{FS=OFS="."} {gsub(/./,"x",$2)} 1' file
Hello.xxxxxxxxxxxxxxxxxx.SecondString
Hello.xxxxxxxxxxx.SecondString