Sed替换2个已知模式之间的可变长度字符串

Sed替换2个已知模式之间的可变长度字符串,sed,Sed,我希望能够替换两个已知模式之间的字符串。问题是我想用一个长度相同的字符串替换它,该字符串只由“x”组成 假设我有一个包含以下内容的文件: Hello.StringToBeReplaced.SecondString Hello.ShortString.SecondString 我希望输出如下: Hello.xxxxxxxxxxxxxxxxxx.SecondString Hello.xxxxxxxxxxx.SecondString 我会选择perl: perl -pe 's/(?<=Hel

我希望能够替换两个已知模式之间的字符串。问题是我想用一个长度相同的字符串替换它,该字符串只由“x”组成

假设我有一个包含以下内容的文件:

Hello.StringToBeReplaced.SecondString
Hello.ShortString.SecondString
我希望输出如下:

Hello.xxxxxxxxxxxxxxxxxx.SecondString
Hello.xxxxxxxxxxx.SecondString
我会选择perl:

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

perl-pe的/(?这个
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
awk-F.{for(i=1;i使用
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
时,它停止替换

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

使用保留空间 建议在
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
和尾标记

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

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.
”和下一个点“
”之间要替换的字符数

有时候,让你的问题足够接近真实场景是很重要的——这可能就是这样一种情况
sed
regular expressions(在大多数其他正则表达式方言中,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'
脚本中的更改包括:

sed ':a;s/\(1\.2\.840\.x*\)[^x.]\([^.]*\.\)/\1x\2/;t a'
  • 添加
    1\.2\.840\.
    作为开始模式
  • 将“要替换的字符”表达式修改为“非
    x
  • 只使用
    \.
    作为尾部模式
  • 如果您确定只需要匹配数字,则可以将
    [^x.]
    替换为
    [0-9]
    ,在这种情况下,您不必担心下面讨论的空格

    您可能决定不希望空格匹配,因此会出现如下随意评论:

    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'
    
    因此,这些更改将一直持续,直到您获得了足够精确的内容,可以在不需要对当前数据集执行任何不需要的操作的情况下执行所需操作为止。编写防弹正则表达式需要精确指定所需匹配的内容,这可能非常困难。

    Bash也可以工作 虽然
    perl
    sed
    awk
    解决方案可能是更好的选择,但是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
    
    $bash replacedot.sh
    为了您的理智,只需使用awk:

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

    我希望awk的
    sprintf
    可以重复字符。现在我也在想
    perl
    是最好的选择。使用GNU awk,您可以捕获正则表达式的一部分:
    gawk'匹配($0,/(.*Hello\)(.+)(\.SecondString.*)/,m{gsub(/,“x”,m[2]);$0=m[1]m[2]m[3]}1'
    看起来比我的尝试更好:
    gsub
    您使用的是解决字符重复的方法。但是我删除了我的答案,因为awk中没有
    (非贪婪)量词。(它实际上是h。)
    $ bash replacedot.sh < dat/replacefile.txt
    Hello.xxxxxxxxxxxxxxxxxx.SecondString
    Hello.xxxxxxxxxxx.SecondString
    
    $ awk 'BEGIN{FS=OFS="."} {gsub(/./,"x",$2)} 1' file
    Hello.xxxxxxxxxxxxxxxxxx.SecondString
    Hello.xxxxxxxxxxx.SecondString