sed sed no cygwin só pode substituir um caractere?

3

Estou tentando substituir um elemento XML em mais de 20 arquivos no Windows usando sed e cygwin. A linha é:

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

Isso não substitui nada. No entanto, se eu tentar:

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

Produz:

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

No entanto, se eu tentar adicionar apenas um único caractere, ele exibirá o documento como está:

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

O acima não faz nada. O que estou fazendo de errado? A chevron é um personagem especial ou estou usando mal o sed? Ou é uma falha no cygwin?

    
por NobleUplift 20.03.2017 / 17:27

2 respostas

10

Provavelmente, esse arquivo é codificado em UTF-16, ou seja, com 2 ou 4 bytes por caractere, provavelmente até mesmo com uma marca de ordem de bytes no começo.

Os caracteres que são mostrados no seu exemplo (todos os caracteres ASCII) são tipicamente codificados em 2 bytes, sendo o primeiro ou o segundo (dependendo se é uma codificação UTF-16 big-enfian ou little-endian) sendo 0 e o outro é o código ASCII / Unicode. O byte 0 é normalmente invisível em um terminal, de modo que o texto aparece OK quando descartado, pois o resto é apenas ASCII, mas o texto contém:

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

Você precisaria converter esse texto para o conjunto de caracteres do seu local para que sed fosse capaz de lidar com ele. Note que o UTF-16 não pode ser usado como uma codificação de caracteres em uma localidade no Unix. Você não encontrará uma localidade que use o UTF-16 como codificação de caracteres.

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

Isso pressupõe que a entrada tenha uma lista de materiais. Se não, você precisa determinar se é big endian ou little endian (provavelmente little endian) e altere esse utf-16 para utf-16le ou utf-16be .

Se o conjunto de caracteres do locale for UTF-8, não deverá haver nada perdido na tradução, mesmo que o texto contenha caracteres não-ASCII.

Como o sed do Cygwin é tipicamente GNU sed , ele também será capaz de lidar com esse tipo de entrada binária (já que contém bytes NUL) por si só, então você também pode fazer algo como:

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

O comando file deve ser capaz de dizer se a entrada é de fato UTF-16. Você pode usar sed -n l ou od -tc para ver esses caracteres NUL ocultos. Exemplo de texto UTF-16 little-endian com BOM:

$ echo true | iconv -t utf-16 | od -tc
0000000 377 376   t  
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
r
<[NUL]S[NUL]t[NUL]a[NUL]r[NUL]t[NUL]W[NUL]h[NUL]e[NUL]n[NUL]...
u
iconv -f utf-16 < Task_01.xml |
  sed 's~<StartWhenAvailable>true</StartWhenAvailable>~<StartWhenAvailable>false</StartWhenAvailable>~g' |
  iconv -t utf-16 > Task_01.xml.out
e
LC_ALL=C sed -i 's/t\x00r\x00u\x00e/f\x00a\x00l\x00s\x00e/g' Task_01.xml
\n
$ echo true | iconv -t utf-16 | od -tc
0000000 377 376   t  
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
r %pre% u %pre% e %pre% \n %pre% 0000014 $ echo true | iconv -t utf-16 | sed -n l 76t%pre%0r%pre%0u%pre%0e%pre%0$ %pre%0$ $ echo true | iconv -t utf-16 | file - /dev/stdin: Little-endian UTF-16 Unicode text, with no line terminators
0000014 $ echo true | iconv -t utf-16 | sed -n l 76t%pre%0r%pre%0u%pre%0e%pre%0$ %pre%0$ $ echo true | iconv -t utf-16 | file - /dev/stdin: Little-endian UTF-16 Unicode text, with no line terminators

Para processar vários arquivos com zsh / bash / ksh93 :

%pre%     
por 20.03.2017 / 18:16
2

Coloque seu comando sed dentro de um arquivo, digamos, sed.cmds & então invoque sed como:

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

Tente também alterar o delimitador para _ , como:

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

    
por 20.03.2017 / 17:46