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


84

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

<username><![CDATA[name]]></username>
<password><![CDATA[password]]></password>
<dbname><![CDATA[name]]></dbname>

并且我需要制作一个脚本,将第一行中的“名称”更改为“ something”,第二行中的“密码”更改为“ somethingelse”,第三行中的“ name”更改为“ somethingdifferent”。我不能依靠它们在文件中出现的顺序,因此我不能简单地将“名称”的第一次出现替换为“某物”,并将第二次出现的“名称”替换为“某物”。我实际上需要搜索周围的字符串,以确保找到并替换了正确的东西。

到目前为止,我已经尝试过以下命令来查找和替换第一个“名称”出现的位置:

sed -i "s/<username><![CDATA[name]]><\/username>/something/g" file.xml

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

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

<username>.+?(name).+?</username>

并用“ something”代替括号中的内容。

这可能吗?


2
只需注意,几乎所有基于正则表达式的解决方案,除非进行了特别的设计,否则都会在输入格式发生更改时冒被破坏的风险。正则表达式对于处理XML,SGML或派生类(在我看来是这样)是一个糟糕的选择。
CVn

已批准!考虑使用XQuery例如:w3schools.com/xquery/default.asp。这是用于检索和处理XML内容的W3C标准。
lgeorget 2013年

Answers:


157
sed -i -E "s/(<username>.+)name(.+<\/username>)/\1something\2/" file.xml

我想这就是您要寻找的。

说明:

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

20
为-E选项+1
slackmart 2013年

4
它会留下一个名为的备份文件(original name) + "-E"
Sarge Borsch,2015年

4
在OSX上,我得到'sed:1:“ s /(<用户名>。+)name(。+ ...”:RE中未定义\ 1。我将这个问题的确切示例粘贴到文件中。我在这个文件上从这个答案中运行了命令,也许OSX具有不同的语法?
deweydb

1
sed的gnu版本支持“ -E”参数,但不是官方的。联机帮助页中甚至没有提及。如果要使用扩展的正则表达式,则必须使用“ -r”参数。
艾肯·克鲁格

3
@deweydb根据此答案,应使用\(and \)代替(and )
张巴兹

14
sed -e '/username/s/CDATA\[name\]/CDATA\[something\]/' \
-e '/password/s/CDATA\[password\]/CDATA\[somethingelse\]/' \
-e '/dbname/s/CDATA\[name\]/CDATA\[somethingdifferent\]/' file.txt

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


1
优雅,高效且完美适合该表壳。+1
lgeorget

6

如果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

上面的方法也可以用正则表达式难以解决的情况下工作:

  • 可以替换标签的值而无需指定其当前值。
  • 即使它们只是转义且未包含在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>

3

您需要\[.*^$/s命令的正则表达式部分,\&/替换部分以及换行符中加引号。正则表达式是基本的正则表达式,此外,您还需要引用s命令的定界符。

您可以选择其他定界符以避免引用/。您必须改为引用该字符,但是通常更改分隔符的目的是选择一个在替换文本或替换文本中都没有出现的分隔符。

sed -e 's~<username><!\[CDATA\[name\]\]></username>~<username><![CDATA[something]]></username>~'

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

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

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

3
$ sed -e '1s/name/something/2' \
      -e '3s/name/somethingdifferent/2' \
      -e 's/password/somethingelse/2' sample.xml

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

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


1

要将“名称”一词替换为“某物”一词,请使用:

sed "s/\(<username><\!\[[A-Z]*\[\)name\]/\1something/g" file.xml

那将替换所有出现的指定单词。

到目前为止,所有内容都已输出到标准输出,您可以使用:

sed "s/\(<username><\!\[[A-Z]*\[\)name\]/\1something/g" file.xml > anotherfile.xml

将更改保存到另一个文件。


0
Usage: sed [OPTION]... {script-only-if-no-other-script} [input-file]...

    -r, --regexp-extended
             use extended regular expressions in the script.

以便替换属性文件中的值

sed -i -r 's/MAIL\=(.+)/MAIL\=user@mymail.com/' etc/service.properties 
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.