cygwin上的sed只能替换一个字符?

cygwin上的sed只能替换一个字符?

我正在尝试使用 sed 和 cygwin 替换 Windows 上 20 多个文件中的 XML 元素。该行是:

cd "D:\Backups\Tasks"
sed -i 's~<StartWhenAvailable>true</StartWhenAvailable>~<StartWhenAvailable>false</StartWhenAvailable>~g' "Task_01.xml"

这不能代替任何东西。但是,如果我尝试:

sed 's~<~[~g' "Task_01.xml"

它输出:

[AllowHardTerminate>true[/AllowHardTerminate>
[StartWhenAvailable>true[/StartWhenAvailable>
[RunOnlyIfNetworkAvailable>false[/RunOnlyIfNetworkAvailable>

但是,如果我尝试仅添加一个字符,它只会按原样输出文档:

sed 's~<B~[B~g' "Task_01.xml"

上面什么也没做。我究竟做错了什么?雪佛龙是特殊字符还是我误用了 sed?或者是cygwin的错误?

答案1

最有可能的是,该文件采用 UTF-16 编码,即每个字符 2 或 4 个字节,甚至可能在开头带有字节顺序标记。

示例中显示的字符(所有 ASCII 字符)通常使用 2 个字节进行编码,其中第一个或第二个字节(取决于它是 big-enfian 还是 little-endian UTF-16 编码)为 0,另一个为 0是 ASCII/Unicode 代码。 0 字节通常在终端上不可见,因此当转储到那里时,文本显示正常,因为其余部分只是 ASCII,但实际上文本包含:

<[NUL]S[NUL]t[NUL]a[NUL]r[NUL]t[NUL]W[NUL]h[NUL]e[NUL]n[NUL]...

您需要将该文本转换为您所在区域的字符集才能sed处理它。请注意,UTF-16 不能用作 Unix 区域设置中的字符编码。您不会找到使用 UTF-16 作为其字符编码的区域设置。

iconv -f utf-16 < Task_01.xml |
  sed 's~<StartWhenAvailable>true</StartWhenAvailable>~<StartWhenAvailable>false</StartWhenAvailable>~g' |
  iconv -t utf-16 > Task_01.xml.out

假设输入有 BOM。如果不是,您需要确定它是大端还是小端(可能是小端)并将其更改utf-16utf-16leor utf-16be

如果语言环境的字符集是 UTF-8,即使文本包含非 ASCII 字符,翻译过程中也不应该丢失任何内容。

由于 Cygwinsed通常是 GNU sed,因此它也能够自行处理该类型的二进制输入(因为它包含 NUL 字节),因此您还可以执行以下操作:

LC_ALL=C sed -i 's/t\x00r\x00u\x00e/f\x00a\x00l\x00s\x00e/g' Task_01.xml

file命令应该能够告诉您输入是否确实是 UTF-16。您可以使用sed -n lod -tc查看那些隐藏的 NUL 字符。带 BOM 的小端 UTF-16 文本示例:

$ echo true | iconv -t utf-16 | od -tc
0000000 377 376   t  \0   r  \0   u  \0   e  \0  \n  \0
0000014
$ echo true | iconv -t utf-16 | sed -n l
\377\376t\000r\000u\000e\000$
\000$
$ echo true | iconv -t utf-16 | file -
/dev/stdin: Little-endian UTF-16 Unicode text, with no line terminators

要使用zsh//处理多个文件:bashksh93

set -o pipefail
for file in ./*.xml; do
  cp -ai "$file" "$file.bak" &&
    iconv -f utf-16 < "$file.bak" |
      sed 's~<StartWhenAvailable>true</StartWhenAvailable>~<StartWhenAvailable>false</StartWhenAvailable>~g' |
      iconv -t utf-16 > "$file" &&
    rm -f "$file.bak"
done

答案2

sed命令放入文件中,例如 sed.cmds,然后调用sed为:

sed -i -f "sed.cmds" "MyFile.xml"

还尝试将分隔符更改为_,如下所示:

s_<BooleanTag>true</BooleanTag>_<BooleanTag>false</BooleanTag>_g

相关内容