使用 sed 查找和替换复杂字符串(最好使用正则表达式)

Har*_*cle 159 sed quoting regular-expression

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

<username><![CDATA[name]]></username>
<password><![CDATA[password]]></password>
<dbname><![CDATA[name]]></dbname>
Run Code Online (Sandbox Code Playgroud)

我需要制作一个脚本,将第一行中的“name”更改为“something”,将第二行中的“password”更改为“somethingelse”,将第三行中的“name”更改为“somethingdifferent”。我不能依赖这些在文件中出现的顺序,所以我不能简单地用“something”替换第一次出现的“name”,用“somethingdifferent”替换第二次出现的“name”。我实际上需要搜索周围的字符串,以确保我找到并替换了正确的东西。

到目前为止,我已尝试使用此命令来查找和替换第一个“名称”出现:

sed -i "s/<username><![CDATA[name]]><\/username>/something/g" file.xml
Run Code Online (Sandbox Code Playgroud)

但是它不起作用所以我认为其中一些字符可能需要转义等。

理想情况下,我希望能够使用正则表达式来匹配两个“用户名”出现并仅替换“名称”。像这样的东西,但有sed

<username>.+?(name).+?</username>
Run Code Online (Sandbox Code Playgroud)

并将括号中的内容替换为“某物”。

这可能吗?

lge*_*get 282

sed -i -E "s/(<username>.+)name(.+<\/username>)/\1something\2/" file.xml
Run Code Online (Sandbox Code Playgroud)

我想,这就是你要找的。

解释:

  • 第一部分中的括号定义了可以在第二部分中重用的组(实际上是字符串)
  • \1\2等在第二部分是在第一部分捕获的第i组的引用(编号从1开始)
  • -E启用扩展的正则表达式(需要+和分组)。

  • +1 表示 -E 选项 (52认同)
  • 在 OSX 上,我得到 'sed: 1: "s/(&lt;username&gt;.+)name(.+ ...": \1 not defined in the RE'。我将这个问题的确切示例粘贴到文件中。然后我在那个文件上运行了这个答案的命令。也许 OSX 有不同的语法? (8认同)
  • 它留下了一个备份文件,名称为`(原始名称)+“-E”`。 (5认同)
  • @deweydb 根据 [this answer](/sf/ask/1730237351/),你应该使用 `\(` 和 `\ )` 而不是 `(` 和 `)`。 (5认同)
  • 在 OSX 上,`-i` 需要一个“扩展”,你可以使用它来使用空字符串:`sed -E -i ''` (3认同)
  • sed 的 gnu 版本支持“-E”参数,但不是官方的。手册页中甚至没有提到它。如果您想使用扩展的正则表达式,则必须改用“-r”参数。 (2认同)
  • 对于像我一样遇到此问题的任何人,我找到了一个非常有用的工具,可以帮助您查看 sed 表达式正在做什么,并帮助提供错误反馈:https://sed.js.org/ (2认同)

evi*_*oup 19

sed -e '/username/s/CDATA\[name\]/CDATA\[something\]/' \
-e '/password/s/CDATA\[password\]/CDATA\[somethingelse\]/' \
-e '/dbname/s/CDATA\[name\]/CDATA\[somethingdifferent\]/' file.txt
Run Code Online (Sandbox Code Playgroud)

/username/s告诉sed只工作在包含字符串“用户名”行。


man*_*ork 11

如果sed不是硬性要求,最好使用专用工具。

如果您的文件是有效的 XML(不仅仅是那 3 个看起来像 XML 的标签),那么您可以使用XMLStarlet

xml ed -P -O -L \
  -u '//username/text()' -v 'something' \
  -u '//password/text()' -v 'somethingelse' \
  -u '//dbname/text()' -v 'somethingdifferent' file.xml
Run Code Online (Sandbox Code Playgroud)

上述内容也适用于使用正则表达式难以解决的情况:

  • 可以在不指定当前值的情况下替换标签的值。
  • 可以替换这些值,即使它们只是被转义而不是包含在 CDATA 中。
  • 即使标签具有属性,也可以替换值。
  • 如果有多个具有相同名称的标签,则可以轻松替换仅出现的标签。
  • 可以通过缩进来格式化修改后的 XML。

以上简单演示:

bash-4.2$ cat file.xml
<sith>
<master>
<username><![CDATA[name]]></username>
</master>
<apprentice>
<username><![CDATA[name]]></username>
<password>password</password>
<dbname foo="bar"><![CDATA[name]]></dbname>
</apprentice>
</sith>

bash-4.2$ xml ed -O -u '//apprentice/username/text()' -v 'something' -u '//password/text()' -v 'somethingelse' -u '//dbname/text()' -v 'somethingdifferent' file.xml
<sith>
  <master>
    <username><![CDATA[name]]></username>
  </master>
  <apprentice>
    <username><![CDATA[something]]></username>
    <password>somethingelse</password>
    <dbname foo="bar"><![CDATA[somethingdifferent]]></dbname>
  </apprentice>
</sith>
Run Code Online (Sandbox Code Playgroud)


Gil*_*il' 5

您需要\[.*^$/在命令的正则表达式部分s\&/替换部分中引用,并加上换行符。正则表达式是一个基本的正则表达式,此外还需要引用s命令的分隔符。

您可以选择不同的分隔符以避免引用/。您必须改为引用该字符,但通常更改分隔符的目的是选择要替换的文本或替换文本中未出现的字符。

sed -e 's~<username><!\[CDATA\[name\]\]></username>~<username><![CDATA[something]]></username>~'
Run Code Online (Sandbox Code Playgroud)

您可以使用组来避免重复替换文本中的某些部分,并适应这些部分的变化。

sed -e 's~\(<username><!\[[A-Z]*\[\)name\(\]\]></username>\)~\1something\2~'

sed -e 's~\(<username>.*[^A-Za-z]\[\)name\([^A-Za-z].*</username>\)~\1something\2~'
Run Code Online (Sandbox Code Playgroud)


小智 5

$ sed -e '1s/name/something/2' \
      -e '3s/name/somethingdifferent/2' \
      -e 's/password/somethingelse/2' sample.xml
Run Code Online (Sandbox Code Playgroud)

您可以简单地使用“s”前面的数字中的地址,该数字表示行号。

最后的数字也告诉sed替换第二个匹配而不是替换第一个匹配。