1

我想做的是:

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

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

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

4

1 回答 1

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 会认为我们正在尝试访问 $!变量(执行最后一个异步命令的结果)。

于 2012-09-28T16:59:03.513 回答