Sed - 如何替换两个字符串,但保留它们之间的内容?

Sed - 如何替换两个字符串,但保留它们之间的内容?

我正在编写一个 shell 脚本来将一些 DokuWiki 页面转换为 MediaWiki 格式并反之亦然。我在脚注方面遇到了一些麻烦。

DokuWiki 有一个插件,可以为 DokuWiki 的基本脚注标记添加扩展功能。其中之一是能够向笔记添加名称并在以后重复使用。例如:

多库维基 媒体维基
[(FOO>This is a footnote.)] <ref name="FOO">This is a footnote.</ref>
[(BAR>Another note in the same paragraph.)] <ref name="BAR">Another note in the same paragraph.</ref>

sed很容易找到和更换。这是我的脚本与带有“注释名称”的脚注相关的命令:

sed -ri 's@\[\(.*>@<ref name=\"XXX\">@g' dokuwiki-page.txt
sed -ri 's@\)\]@<\/ref>@g' dokuwiki-page.txt

但是,当然,它不会保留名称,它只是将新的通用注释名称“XXX”应用于所有具有名称的注释。因此,在我上面的示例中,结果将是:

多库维基 媒体维基
[(FOO>This is a footnote.)] <ref name="XXX">This is a footnote.</ref>
[(BAR>Another note in the same paragraph.)] <ref name="XXX">Another note in the same paragraph.</ref>

我需要帮助来保留注释名称(示例中的 FOO 和 BAR)。我确实接受其他解决方案,而不仅仅是sed

重要笔记:

  1. 脚注句子可以出现在段落的中间,并且多个带有脚注名称的引用可以出现在同一段落中但具有不同的名称。 (又名 Unix 的“非常长的行”的段落)
  2. 我无法拆分命令以[(在第一个命令中替换,>然后在第二个命令中替换,因为 MediaWiki 标记使用了太多的 html 标签(充满<>)。一个标签可能会被不正确地替换。
  3. 有一些[(...)]没有>里面的。就像在 中[(This is a nameless note.)]而不是[(My_Note_Name>This is a named note.)].

答案1

perl使用具有非贪婪重复运算符的正则表达式,这种事情要容易得多:

perl -i -pe 's{\[\((.*?)>(.*?)\)\]}{<ref name="$1">$2</ref>}g' your-file

请注意,-i-r是非标准sed选项。-i实际上是由perl一些实现复制的,尽管彼此之间的方式不兼容。

perl与几种sed实现相反,它对行的大小也没有限制,可以处理 NUL 字符,并且默认情况下按字节处理输入,因此不存在无法在用户区域设置中解码为文本的输入问题。

如果您的输入可能有一些[(...)]不包含>s,那么您需要调整正则表达式。如果引用标签仅包含word 字符(ASCII 数字和下划线),那么它可能只是:

perl -i -pe 's{\[\((\w+)>(.*?)\)\]}{<ref name="$1">$2</ref>}g' your-file

另一种方法是找到所有[(...)]并在其中进行替换作为单独的步骤:

perl -i -pe '
  s{\[\(.*?\)\]}{
    $& =~ s{\[\((.*?)>(.*)\)\]}{<ref name="$1">$2</ref>}r
  }ge' your-file

这也将允许使用将无名注释更改为<ref>nameless</ref>

perl -i -pe '
  s{\[\(.*?\)\]}{
    $& =~ s{\[\((?:(.*?)>)?(.*)\)\]}{
      "<ref" . (defined($1) ? qq( name="$1") : "") . ">$2</ref>"
    }re
  }ge' your-file

或者使用负向前看运算符来确保在[(...)]不包含的内容中匹配)]

perl -i -pe 's{\[\(((?:(?!\)\]).)*?)>((?1))\)\]}{<ref name="$1">$2</ref>}g' your-file

答案2

最终的SED方式:

我通过使用找到了解决方案sed和正则表达式组。

sed -Ei 's@\[\(([[:alnum:]_-]*)>([[:alnum:][:space:].!?:;,@#%$&<>-_]*)\)\]@<ref name=\"\1\">\2<\/ref>@g' dokuwiki-page.txt

解释:

  1. 查找带有[(+ letters, numbers, underscores and dashes in any quantity+ >+ letters, numbers, spaces and punctuation+的行)]
    • 第1组:任意数量的字母、数字、下划线和破折号。
    • 第2组:任意数量的字母、数字、空格和大多数标点符号。由于某种原因,[:punct:]效果不佳,我应该使用一个大列表:.!?:;,@#%$&<>-_
    • 这里的技巧是可以使用\1或来引用组\2。这就像将其存储在变量中。
    • 我不能使用.*代替,([[:alnum:]_-]*)因为它包括其他>.因此,如果同一段落中有任何其他命名脚注(也称为非常长的行),则正则表达式将包含从第一个脚注到第二个脚注末尾的所有内容。一团糟!
  2. 将所有这些替换为<ref name="+ group \1+ ">+ group \2+ </ref>
    • 在这里,我使用\1\2来反向引用我想要保留的内容,同时替换它周围的内容。

非常非常难!我花了三天的时间研究才弄清楚如何做到这一点。而且这么长。最好选择 perl。但是,如果您知道 sed 的更简单方法,请教我,我喜欢学习!

阅读建议:

  • 多尔蒂,D. 和罗宾斯,A. (1997)。 SED 和 AWK。 (第二版)。奥莱利.

相关内容