【问题标题】:Regex find and replace over multi lines in Shell正则表达式在 Shell 中查找和替换多行
【发布时间】:2012-07-27 14:27:41
【问题描述】:

我的问题与shell script: search and replace over multiple lines 类似,但有一个小例外。

在链接的问题中,用户想要这样做:

source:
[stuff before]
<!--WIERD_SPECIAL_COMMENT_BEGIN-->
  [stuff here, possibly multiple lines.
<!--WIERD_SPECIAL_COMMENT_END-->
[stuff after]    

target:
[stuff before]
[new content]
[stuff after]

我的问题类似,我想这样做:

source:
[stuff before]
<!--WIERD_SPECIAL_COMMENT_BEGIN-->
  [this]
<!--WIERD_SPECIAL_COMMENT_END-->
<!--WIERD_SPECIAL_COMMENT_BEGIN-->
  [not this]
<!--WIERD_SPECIAL_COMMENT_END-->
[stuff after]    

target:
[stuff before]
[new content]
<!--WIERD_SPECIAL_COMMENT_BEGIN-->
  [not this]
<!--WIERD_SPECIAL_COMMENT_END-->
[stuff after]

在适当的多行正则表达式中,这很容易做到:

/<!--WIERD_SPECIAL_COMMENT_BEGIN-->.*[this].*<!--WIERD_SPECIAL_COMMENT_END-->/m

但链接问题中建议的答案使用正则表达式作为不允许检查两个外围边界之间的线的范围。

有没有办法将一个范围内的所有行添加到模式缓冲区,以便我可以一次对所有行进行正则表达式?例如:

sed '
    #range between comment beginning and comment end
    /<!--WIERD_SPECIAL_COMMENT_BEGIN-->/,/<!--WIERD_SPECIAL_COMMENT_END-->/
    #Do something to add the lines in this range to pattern buffer
    /.*[this].*/d
    #Delete all the lines if [this] is in the pattern buffer
' <in.txt >out.txt

【问题讨论】:

  • 您的“简单”正则表达式可能无法达到您的预期:它可能会找到一个从 first 开始注释并在 第三个结束的区域 结束评论。
  • 有什么建设性的说法吗?或许能告诉我为什么那可能不像我想的那样?
  • 我确实有一个解决方案成功了一半,但解决我自己的问题却遇到了麻烦。 ;) 原因是正则表达式和区域都是 greedy:它们总是匹配可能的 longest 模式,即使这意味着跳过“end”模式到达那里。

标签: regex shell sed


【解决方案1】:

使用 Perl,相对简单。

perl -0777pe 's/<!--BEGIN-->\n(?:(?!<!--END-->\n).)*?\[this\].*?\n<!--END-->\n/[new content]\n/s' in.txt

Perl 提供的好处是 (a) -0777“slurp 模式”,它一次性拉入整个输入文件,而不是 sed 的一次一行处理; (b) /s 正则表达式标志,它允许点匹配换行符; (c) 吝啬的重复运算符*? 和朋友,这导致重复匹配尽可能少而不是尽可能多;最后是 (d) 否定前瞻(?!...),它允许您在否定前瞻表达式匹配的地方禁止匹配。 (没有这个,如果在“stuff before”文本中有一个“假”起始分隔符,即使是吝啬的匹配也会匹配结束分隔符。)......当然,(e)一种通用编程语言,其中@987654327 @只适用于比较简单的文本处理任务。

(我使用了更简单的开始和结束分隔符。我希望“wierd”是故意拼写错误。)

【讨论】:

    【解决方案2】:

    免责声明:我是初学者。这肯定不是最好的方法。


    我分三步完成了类似的操作。假设您在 Linux 上运行,您可以执行以下操作:

    1) 用特殊字符替换文件中所有出现的换行符:

    cat originalText.txt | tr '\n' '~' > temp
    

    2) 使用您最喜欢的方法(我使用 perl)执行您的正则表达式,在您期望换行符的每个位置放置一个特殊字符的实例。确保保持特殊换行符不变。

    3) 这次反过来执行第一个命令:

    cat temp | tr '~' '\n' > modText.txt
    

    我希望这会有所帮助。

    【讨论】:

    • 一个选项,但很可能失败。正在处理的文件是生成的文件。我选择的 char 实例很可能会在结尾被替换为 \n 而它们不应该被替换
    • 嗯...使用异国情调的角色作为替代品怎么样?我的意思是 ascii 有很多不太可能出现在生成的文件中的替代字符。 (当然,除非它有二进制部分) 编辑:我用不可见的字符(例如“/ 30”)进行了尝试,但没有遇到任何丢失错误。编辑 2:正则表达式支持不可打印字符,特别是使用 \cA 到 \cZ 的 ascii 控制字符。也许试试这些。
    【解决方案3】:

    有没有办法将一个范围内的所有行添加到模式缓冲区,以便我可以一次对所有行进行正则表达式?

    当然,使用保留空间。例如:

    sed -n '/begin/,/end/{ /begin/{h;d};H}; /end/{g;s/\n/<newline>/gp}'
    

    将匹配 'begin' 和 'end' 的行之间的换行符替换为文本 &lt;newline&gt;

    【讨论】:

      【解决方案4】:

      这可能对你有用(GNU sed):

      sed ':a;$!N;/^<!--WIERD_SPECIAL_COMMENT_BEGIN-->/!{P;D};/<!--WIERD_SPECIAL_COMMENT_END-->$/!ba;s/\[this\]/[new content]/;p;d' file
      

      【讨论】:

        【解决方案5】:

        您可以使用sed 这样做:

        parse.sed

        /BEGIN/ {               # If we encounter BEGIN
          :a                    # Read all until END
          N                     # into pattern space
          /END/!ba              # /
          /\[this\]/d           # If the block contains [this], delete it
          s/^/[new content]\n/  # Insert [new content] before the block
        }
        

        像这样运行它:

        sed -f parse.sed infile
        

        输出:

        [stuff before]
        [new content]
        <!--WIERD_SPECIAL_COMMENT_BEGIN-->
          [not this]
        <!--WIERD_SPECIAL_COMMENT_END-->
        [stuff after]
        

        【讨论】:

        • 抱歉,试过了。仅删除带有 [this] 的行。也试过 /.*[this].*/d 同样的事情。似乎为范围内的每一行执行正则表达式删除
        • @MichaelAllen:我已经更新了一个工作代码示例
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-09-20
        • 2015-03-25
        • 2011-06-16
        • 1970-01-01
        • 2017-01-28
        相关资源
        最近更新 更多