cygwinのsedは1文字だけを置き換えることができますか?

cygwinのsedは1文字だけを置き換えることができますか?

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>

ただし、1文字だけを追加しようとすると、文書はそのまま出力されます。

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

上記では何もしません。私は何が間違っていましたか? chevronは特殊文字ですか、それともsedを間違って使用していますか?それともcygwinのバグですか?

ベストアンサー1

ほとんどの場合、ファイルはUTF-16でエンコードされており、文字ごとに2バイトまたは4バイトで、先頭にバイト順のマークが付いている可能性があります。

例に示されている文字(すべてのASCII文字)は通常、2バイトを使用してエンコードされます。ここで、1 番目または 2 番目のバイト (big-enfian または little-endian UTF-16 エンコードかどうかに応じて) は 0 で、残りの A 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-16変更する必要があります。utf-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

このfileコマンドは、入力が実際にUTF-16であるかどうかを通知できます。隠されたNUL文字を使用sed -n lまたは表示できます。od -tcBOMを含む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

//を使用して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

おすすめ記事