【问题标题】:sed to edit only part of a file with regular expressionsed 使用正则表达式仅编辑文件的一部分
【发布时间】:2014-05-07 10:21:59
【问题描述】:

我有一个名为 test.txt 的文件,内容如下

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<test time="60" id="01">
<java.lang.String value="cat"/><java.lang.String value="dog"/>
<java.lang.String value="mouse"/>
<java.lang.String value="cow"/>
</test>

我想做的是,我想编辑文件,以便当我得到类似 &lt;java.lang.String value="something"/&gt; 的内容时,我会将那部分更改为 &lt;animal&gt;something&lt;/animal&gt;

因此,对于前面的示例,在使用 sed/awk/grep 命令应用脚本后,文件内容将更改为或创建一个新文件,如下所示:

   <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
    <test time="60" id="01">
    <animal>cat</animal><animal>dog</animal>
    <animal>mouse</animal>
    <animal>cow</animal>
    </test>

我尝试使用以下命令提取该特定部分:

$less test.txt | grep -Po 'java.lang.String value="\K[^"]*' | awk -F: '{print "<animal>" $1 "</animal>"}'

输出给了我更改的部分,但我希望这个更改的部分与文件的其余部分保持不变:

<animal>cat</animal>
<animal>dog</animal>
<animal>mouse</animal>
<animal>cow</animal>

我是脚本新手,我不知道如何将完整的输出写入文件。

【问题讨论】:

  • 请尝试使用正确的工具来完成正确的任务。转换 XML 文档应使用 XSL 转换完成,而不是 sed/grep/etc.. 有标准的工具,它可以检查您的输入文件,因为它会为您提供正确的结果。

标签: regex linux bash sed grep


【解决方案1】:
sed -r 's#<java.lang.String value="([^"]*)"/>#<animal>\1</animal>#g' test.txt

而且你不应该使用正则表达式进行XML 转换...

编辑它的工作原理

默认情况下sed 使用“基本正则表达式”,其中许多特殊字符必须以\ 为前缀。 -r 标志切换到“扩展正则表达式”,其中语法不那么繁琐。详情请见OpenGroup

默认情况下sed 按原样打印输出,除非命令修改它。替换命令类似于s#search_regexp#replacement#flags。分隔符可以是/#, 之类的任何东西。我选择#,这样它就不会与XML 中的\ 字符冲突。

然后我们匹配 &lt;java.lang.String value="anything_except_quotes"/&gt; 之类的东西。我们要重用的部分有括号,称为匹配组。在替换中,我们通过\1 引用我们在匹配组中捕获的内容。

g 标志使sed 替换所有出现的搜索模式,而不仅仅是第一个。

【讨论】:

  • 谢谢,你能解释一下这是如何给出正确的输出的吗?我不擅长 sed
【解决方案2】:

确定你的命令有一些问题:

less test.txt | grep -Po 'java.lang.String value="\K[^"]*' | awk -F: '{print "<animal>" $1 "</animal>"}'

首先,less 没有用处,grep 可以将文件作为参数:

grep -Po 'java.lang.String value="\K[^"]*' test.txt | awk -F: '{print "<animal>" $1 "</animal>"}'

然后您使用grep 选择与字符串匹配的行,所以基本上,您的命令序列是明确只保留具有java.lang... 字符串的行,其他所有内容out... 一个更简单的解决方案是使用sed:

sed -r 's,<java.lang.String value="([^"]*)"\s*/>,<animal>\1</animal>,g' test.txt

它使用 sed 的替换语法来替换匹配项,同时将括号中的内容提取 () 为右侧部分中的 \1[^"] 部分用于匹配不是" 字符的所有内容,* 运算符用于应用匹配 0 次或更多次。 \s 是匹配一个空格,*,0 次或多次。

正则表达式是一种自动机,它使用状态和转换来匹配给定的字符串。这是正则表达式如何工作的视觉效果:

demo of the regex on an example

虽然在您的特定情况下,简单的正则表达式可以解决,但请记住,这只是一个hack。您应该改用 XML 解析器并替换节点以满足您的需求,使用 XSLT/XSLFO 这些工具旨在将 XML 转换为另一个(或其他)。

为此,您可以使用诸如xsltproc 之类的工具并查看this Q 的示例,该示例将所有foo 节点转换为XML 树中的bar 节点,操作方法如下:

test.xsl:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output indent="yes"/>
  <xsl:strip-space elements="*"/>

  <!--Identity Template. This will copy everything as-is.-->
  <xsl:template match="node()|@*">
    <xsl:copy>
      <xsl:apply-templates select="node()|@*"/>
    </xsl:copy>
  </xsl:template>

  <!--Change "java.lang.String" element to "animal" element.-->
  <xsl:template match="java.lang.String">
    <animal>
      <!-- get the attribute 'value' of java.lang.String -->
      <xsl:copy-of select="@*"/>
      <xsl:apply-templates/>
    </animal>
  </xsl:template>

</xsl:stylesheet>

运行:

xsltproc test.xsl test.xml

结果:

<?xml version="1.0"?>
<test time="60" id="01">
  <animal value="cat"/>
  <animal value="dog"/>
  <animal value="mouse"/>
  <animal value="cow"/>
</test>

顺便说一句,鉴于您的 XML,它看起来像是由 Java 生成的,并且有多种方法可以从 within your code 应用该 XSL,甚至在您需要使用命令行工具处理它之前。

【讨论】:

  • 不适用于 OP 的示例,因为您的 sed 中缺少“g”标志。
  • 你的答案变成了一篇关于XML 操作的论文。 +1 以获得详尽的解释。
  • 呃...我不同意这是一篇关于 XML 的论文 ;-) 我只是在说明使用正确的工具完成正确的任务。
猜你喜欢
  • 1970-01-01
  • 2022-11-23
  • 1970-01-01
  • 2023-03-26
  • 2018-11-29
  • 2019-09-18
  • 1970-01-01
  • 2020-09-29
  • 1970-01-01
相关资源
最近更新 更多