【问题标题】:Find and replace multi line file content in files查找和替换文件中的多行文件内容
【发布时间】:2012-09-08 09:58:55
【问题描述】:

我想做的是:

find some_files -name '*.html' -exec sed -i "s/`cat old`/`cat new`/g" {} \;

oldnew 包含换行符和斜线以及其他特殊字符,这会阻止 sed 正确解析。

我已阅读有关如何使用 sed 转义换行符以及命令 tr、命令 printf '%q' 的信息,但我无法使这些工作正确,也许是因为我不完全了解它们的功能。此外,我不知道我还必须转义哪些特殊字符才能使 sed 工作。

【问题讨论】:

  • 你想达到什么目的?就目前而言,对于每个 html 文件,如果它找到文件 old 的全部内容,它将用文件 new 的全部内容替换它。
  • 你能举个例子说明你想编辑什么
  • Daniel Landau 是的,这正是我想要做的。问题是这些文件包含由 sed 解释的特殊字符(如斜杠),我想将它们转义。
  • 为了更精确,我在执行上述命令时得到的错误是“未终止的`s'命令”。

标签: file replace sed newline multiline


【解决方案1】:

我不确定你到底想做什么,但如果旧文件包含换行符,你可能会遇到麻烦。这是因为 sed 通过在每一行上应用命令来工作,因此除非您显式加载更多行,否则尝试将一行与表示多行的模式匹配是行不通的。

我的建议是在应用替换命令之前将整个文件加载到 sed 的“缓冲区”中。然后,您必须确保正确转义旧的和新的。此外,更令人困惑的是,旧文件(模式)的转义必须不同于新文件(替换)的转义。

让我们首先将新文件转义为“new.tmp”文件。为清楚起见,我们将创建一个名为“escape_new.sed”的 sed 脚本:

#!/bin/sed -f

# Commas used as separators
s,\\,\\\\,g
s,$,\\,g
s,[/&],\\&,g
$ a/

然后运行它:sed -f escape_new.sed new > new.tmp

我们使用三个命令来转义:

  1. 反斜杠前面应该有另一个反斜杠
  2. 换行符之前应该有一个反斜杠(我们通过在行尾添加一个反斜杠来做到这一点)。
  3. & 和斜杠前面应该有一个反斜杠(请注意,替换文本中的 & 实际上是一个包含匹配项的运算符,因此如果它匹配斜杠它包含斜杠,如果它匹配 & 符号,它包含& 号)。
  4. 在最后一行(用“$”符号表示),我们附加(通过“a”命令)一个斜杠。这是我们稍后将使用的替换命令的结束斜线。我们必须把它放在这里,因为反引号会删除输入末尾的任何额外换行符,这可能会导致问题(例如,用于引用换行符的反斜杠实际上引用了终止斜杠)。

现在让我们转义旧文件。如上所述,我们将创建一个“escape_old.sed”脚本。不过,在我们这样做之前,我们需要将整个文件加载到模式空间(sed 的内部缓冲区)中,以便我们可以替换换行符。我们可以使用以下命令来做到这一点:

: a
$! {
    N
    b a
}

第一个命令创建一个名为“a”的标签。第二个命令(“{”)实际上启动了一组命令。这里的魔力是“$!”地址前缀。该前缀告诉它只有在读取的最后一个输入行不是输入的最后一行时才运行命令(“$”表示输入的最后一行,“!”表示不是)。该组中的第一个命令将输入​​中的下一行附加到模式空间中。如果这个“N”命令在最后一行执行,它会终止脚本,所以我们必须小心不要在最后一行执行它。该组中的第二个命令是分支命令“b”,它将“跳转”回“a”标签。神奇的是“$!”我们在命令之前的地址前缀。右括号关闭组。该组具有各自的地址前缀,允许我们遍历所有行,将它们连接在一起,并在最后一行之后停止,允许执行任何进一步的命令。然后我们就有了最终的脚本:

#!/bin/sed -f

: a
$! {
    N
    b a
}

s,\\,\\\\,g
s,\n,\\n,g
s,[][/^$.],\\&,g

如上,我们需要对特殊字符进行转义。在这种情况下,实际的换行符现在被转义为反斜杠,后跟字母 n。在最后一个命令中,还有更多的字符需要以反斜杠作为前缀。请注意,要匹配右方括号,它必须是方括号内的第一个字符,以防止 sed 将其解释为我们要匹配的字符列表的关闭字符。因此,方括号之间按顺序列出的字符是][/^$.

我们再次执行它:sed -f escape_new.sed old > old.tmp

现在我们可以在 sed 命令中使用这些转义文件,但是我们必须再次将所有行加载到模式空间中。使用与以前相同的命令,但将它们放在一行中,我们得到了紧凑的形式::a;$!{N;ba}: 现在我们可以在最终表达式中使用它(没有现在 new.tmp 文件中的右斜杠字符):

find some_files -name '*.html' -exec sed -e ":a;\$!{N;ba};s/`cat old.tmp`/`cat new.tmp`g" -i {} \;

希望它会起作用 =)

请注意,我们使用反斜杠转义了$ 符号,否则shell 会认为我们正在尝试访问$! 变量(执行最后一个异步命令的结果)。

【讨论】:

  • 是的,这正是我想要做的,你的答案解释得很好。 :)
  • 现在它几乎可以工作了。我用不同的文件样本做了一些测试。它不适用于多行文件,但当我在标准输入中输入多行文本时它确实有效。 cat 命令似乎在找到换行符时终止命令行。
  • 您能否提供一个无效的输入组合示例,好吗?它可以帮助找到问题。谢谢=)
  • 我一定是错的,问题不在于 cat 命令。这是我刚刚尝试过的:我创建了一个 HTML 文件,其中包含:第一行的“beginning”,第二行的“oldline1”,第三行的“oldline2”,然后是其他一些文本。我还创建了一个文件“old”,第一行包含“oldline1”,第二行包含“oldline2”,第一行包含“newline1”,第二行包含“newline2”。然后我运行你的脚本来生成 old.tmp 和 new.tmp。我终于运行了最后一个命令行。执行时没有错误,但 HTML 文件未更改。
  • 但我忘了提到我必须删除 new.tmp 最后一行末尾的反斜杠。在我这样做之前,我遇到了一个错误“sed: -e expression #1, char 54: unterminated `s' command”(可能是因为反斜杠逃脱了 OEF)。然后我在 old.tmp 中只用“oldline1”做了另一个测试,它正确地用我的 HTML 文件中的两行“newline1”和“newline2”替换了“oldline1”。所以捕捉'\n'肯定有问题。
猜你喜欢
  • 2019-07-07
  • 2021-11-29
  • 1970-01-01
  • 2014-03-05
  • 2011-07-29
  • 2011-08-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多