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

Nob*_*ift 2 xml character-encoding sed cygwin windows

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

cd "D:\Backups\Tasks"
sed -i 's~<StartWhenAvailable>true</StartWhenAvailable>~<StartWhenAvailable>false</StartWhenAvailable>~g' "Task_01.xml"
Run Code Online (Sandbox Code Playgroud)

这什么都代替不了。但是,如果我尝试:

sed 's~<~[~g' "Task_01.xml"
Run Code Online (Sandbox Code Playgroud)

它输出:

[AllowHardTerminate>true[/AllowHardTerminate>
[StartWhenAvailable>true[/StartWhenAvailable>
[RunOnlyIfNetworkAvailable>false[/RunOnlyIfNetworkAvailable>
Run Code Online (Sandbox Code Playgroud)

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

sed 's~<B~[B~g' "Task_01.xml"
Run Code Online (Sandbox Code Playgroud)

以上没有任何作用。我究竟做错了什么?人字形是特殊字符还是我误用了 sed?或者是cygwin的错误?

Sté*_*las 10

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

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

<[NUL]S[NUL]t[NUL]a[NUL]r[NUL]t[NUL]W[NUL]h[NUL]e[NUL]n[NUL]...
Run Code Online (Sandbox Code Playgroud)

您需要将该文本转换为您的语言环境的字符集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
Run Code Online (Sandbox Code Playgroud)

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

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

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

LC_ALL=C sed -i 's/t\x00r\x00u\x00e/f\x00a\x00l\x00s\x00e/g' Task_01.xml
Run Code Online (Sandbox Code Playgroud)

file命令应该能够告诉您输入是否确实是 UTF-16。您可以使用sed -n lod -tc查看那些隐藏的 NUL 字符。带有 BOM 的 little-endian 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
Run Code Online (Sandbox Code Playgroud)

为了处理与几个文件zsh/ bash/ ksh93

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
Run Code Online (Sandbox Code Playgroud)