Erro RE: sequência de bytes ilegais no Mac OS X

184

Estou tentando substituir uma string em um Makefile no Mac OS X para compilação cruzada no iOS. A cadeia incorporou aspas duplas. O comando é:

sed -i "" 's|"iphoneos-cross","llvm-gcc:-O3|"iphoneos-cross","clang:-Os|g' Configure

E o erro é:

sed: RE error: illegal byte sequence

Tentei escapar das aspas duplas, vírgulas, traços e dois pontos sem alegria. Por exemplo:

sed -i "" 's|\"iphoneos-cross\"\,\"llvm-gcc\:\-O3|\"iphoneos-cross\"\,\"clang\:\-Os|g' Configure

Estou tendo um bom tempo depurando o problema. Alguém sabe como sedimprimir a posição da sequência de bytes ilegal? Ou alguém sabe qual é a sequência ilegal de bytes?

jww
fonte
2
A sequência ilegal de bytes parece algo que você obtém ao alimentar ascii de 8 bits para algo que espera utf-8.
Klas Lindbäck
36
Você pode tentar:LC_CTYPE=C && LANG=C && sed command
anubhava
5
Obrigado pessoal. Era a LANGcoisa. Suspiro ....
JWW
3
@ user2719058: O BSD sed(como também usado no OS X) requer -i ''(argumento de opção de cadeia vazia e separado) para atualização no local sem um arquivo de backup; com GNU sed, Só -ipor si só funciona - ver stackoverflow.com/a/40777793/45375
mklement0
1
Mais um para a coisa LANG. Que pena, isso é obscuro, não óbvio e surpreendentemente difícil de pesquisar.
Spudley

Respostas:

300

Um comando de amostra que exibe o sintoma: sed 's/./@/' <<<$'\xfc'falha, porque o byte 0xfcnão é um caractere UTF-8 válido.
Observe que, por outro lado, o GNU sed (Linux, mas também instalável no macOS) simplesmente passa o byte inválido, sem relatar um erro.

Usar a resposta anteriormente aceita é uma opção se você não se importa em perder o suporte para o seu local verdadeiro (se você estiver em um sistema nos EUA e nunca precisar lidar com caracteres estrangeiros, isso pode ser bom).

No entanto, o mesmo efeito pode ser tido ad-hoc para um único comando única :

LC_ALL=C sed -i "" 's|"iphoneos-cross","llvm-gcc:-O3|"iphoneos-cross","clang:-Os|g' Configure

Nota: O que importa é uma configuração efetiva LC_CTYPE de C, portanto LC_CTYPE=C sed ..., normalmente também funcionaria, mas, se LC_ALLfor definido (para algo diferente de C), ele substituirá as LC_*variáveis ​​de categoria individual , como LC_CTYPE. Assim, a abordagem mais robusta é definir LC_ALL.

No entanto, a configuração (efetivamente) LC_CTYPEde Ctratar sequências de caracteres como se cada byte fosse seu próprio caractere ( nenhuma interpretação baseada em regras de codificação é executada), sem levar em consideração a codificação - multibyte-on-demand - UTF-8 que o OS X emprega por padrão , onde caracteres estrangeiros têm codificações multibyte .

Em poucas palavras: a configuração LC_CTYPEparaC faz com que o shell e os utilitários reconheçam apenas letras em inglês básicas como letras (aquelas no intervalo ASCII de 7 bits), de modo que caracteres estrangeiros. não serão tratados como letras , causando, por exemplo, conversões em maiúsculas / minúsculas.

Novamente, isso pode ser bom se você não precisar corresponder a caracteres codificados com vários bytes, como por exemplo é, e simplesmente desejar passar esses caracteres .

Se isso for insuficiente e / ou você quiser entender a causa do erro original (incluindo a determinação de quais bytes de entrada causaram o problema) e executar conversões de codificação sob demanda, leia abaixo.


O problema é que a codificação do arquivo de entrada não corresponde à do shell.
Mais especificamente, o arquivo de entrada contém caracteres codificados de uma maneira que não é válida em UTF-8 (como @Klas Lindbäck afirmou em um comentário) - é o que a sedmensagem de erro está tentando dizer invalid byte sequence.

Muito provavelmente, seu arquivo de entrada usa uma codificação de 8 bits de byte único , como ISO-8859-1frequentemente usada para codificar idiomas da "Europa Ocidental".

Exemplo:

A letra acentuada àpossui o ponto de código Unicode 0xE0(224) - o mesmo que em ISO-8859-1. No entanto, devido à natureza da codificação UTF-8 , esse único ponto de código é representado como 2 bytes - 0xC3 0xA0, enquanto a tentativa de passar o byte único 0xE0 é inválida em UTF-8.

Aqui está uma demonstração do problema usando a string voilàcodificada como ISO-8859-1, com a àrepresentada como um byte (por meio de uma string bash com citação ANSI-C ( $'...') usada \x{e0}para criar o byte):

Observe que o sedcomando é efetivamente um no-op que simplesmente passa a entrada, mas precisamos provocar o erro:

  # -> 'illegal byte sequence': byte 0xE0 is not a valid char.
sed 's/.*/&/' <<<$'voil\x{e0}'

Para simplesmente ignorar o problema , a LCTYPE=Cabordagem acima pode ser usada:

  # No error, bytes are passed through ('á' will render as '?', though).
LC_CTYPE=C sed 's/.*/&/' <<<$'voil\x{e0}'

Se você deseja determinar quais partes da entrada causam o problema , tente o seguinte:

  # Convert bytes in the 8-bit range (high bit set) to hex. representation.
  # -> 'voil\x{e0}'
iconv -f ASCII --byte-subst='\x{%02x}' <<<$'voil\x{e0}'

A saída mostrará todos os bytes com o bit alto definido (bytes que excedem o intervalo ASCII de 7 bits) na forma hexadecimal. (Observe, no entanto, que isso também inclui sequências multibyte UTF-8 codificadas corretamente - uma abordagem mais sofisticada seria necessária para identificar especificamente bytes inválidos em UTF-8.)


Executando conversões de codificação sob demanda :

O utilitário padrão iconvpode ser usado para converter para codificações ( -t) e / ou de ( -f); iconv -llista todos os suportados.

Exemplos:

Converta FROM ISO-8859-1na codificação em vigor no shell (com base em LC_CTYPE, que é UTF-8baseada em padrão), com base no exemplo acima:

  # Converts to UTF-8; output renders correctly as 'voilà'
sed 's/.*/&/' <<<"$(iconv -f ISO-8859-1 <<<$'voil\x{e0}')"

Observe que essa conversão permite corresponder corretamente caracteres estrangeiros :

  # Correctly matches 'à' and replaces it with 'ü': -> 'voilü'
sed 's/à/ü/' <<<"$(iconv -f ISO-8859-1 <<<$'voil\x{e0}')"

Para converter a entrada BACK para ISO-8859-1após o processamento, basta canalizar o resultado para outro iconvcomando:

sed 's/à/ü/' <<<"$(iconv -f ISO-8859-1 <<<$'voil\x{e0}')" | iconv -t ISO-8859-1
mklement0
fonte
4
Eu diria que esta é uma opção muito melhor. Primeiro, eu não gostaria de perder o suporte multilíngue em todo o Terminal. Segundo, a resposta aceita parece uma solução global para um problema local - algo a ser evitado.
2828 Alex
Eu tive alguns pequenos ajustes nisso. Eu gostaria de receber feedback. stackoverflow.com/a/35046218/9636
Heath Borders
LC_CTYPE=C sed 's/.*/&/' <<<$'voil\x{e0}'imprime sed: RE error: illegal byte sequencepara mim na Sierra. echo $LC_ALLgera en_US.UTF-8FWIW.
ahcox
1
@ahcox: Sim, porque a configuração LC_ALL substitui todas as outras LC_*variáveis, incluindo LC_CTYPE, conforme explicado na resposta.
precisa saber é o seguinte
2
@ mklement0 Legal, isso funciona: "LC_ALL = C sed 's /.*/&/' <<< $ 'voil \ x {e0}'". Precedência explicado aqui para os meus colegas ignorantes desatentos: pubs.opengroup.org/onlinepubs/7908799/xbd/envvar.html
ahcox
142

Adicione as seguintes linhas ao seu ~/.bash_profileou ~/.zshrcarquivo (s).

export LC_CTYPE=C 
export LANG=C
binarytemple_picsolve
fonte
29
realmente funciona, mas você poderia explicar o porquê?
Hoang Pham
11
@HoangPham: Definir LC_CTYPEcomo Cfaz com que cada byte nas strings tenha seu próprio caractere sem aplicar nenhuma regra de codificação. Como uma violação das regras de codificação (UTF-8) causou o problema original, isso faz com que o problema desapareça. No entanto, o preço pago é que o shell e os utilitários somente reconheçam as letras básicas em inglês (as da faixa ASCII de 7 bits) como letras. Veja minha resposta para mais.
mklement0
6
Definir isso permanentemente nos arquivos de inicialização do seu shell desabilitará muitos comportamentos úteis. Você deseja colocar isso apenas em comandos individuais que exigem absolutamente isso.
Tripleee
4
Muito perigoso pode causar consequências inesperadas. Pode-se usar LC_CTYPE=C sed …, ou seja, apenas no comando sed.
Yongwei Wu
2
Isso desativará completamente o suporte a caracteres Unicode no seu shell. Adeus emojis, caracteres extravagantes de desenho de linhas, letras com sotaques, .... Muito melhor definir isso apenas para o comando sed, conforme descrito em outras respostas.
asmeurer
6

Minha solução alternativa estava usando o Perl:

find . -type f -print0 | xargs -0 perl -pi -e 's/was/now/g'
Vitaly Zdanevich
fonte
Este funciona muito bem. E não tive erros ao escapar de caracteres especiais, ao contrário dos outros. Os anteriores me deram problemas como "sed: RE error: seqüência de bytes ilegal" ou sed: 1: "path_to_file": código de comando inválido.
JMags1632
3

A resposta do mklement0 é ótima, mas tenho alguns pequenos ajustes.

Parece uma boa idéia especificar explicitamente basha codificação ao usar iconv. Além disso, devemos acrescentar uma marca de ordem de bytes ( mesmo que o padrão unicode não a recomende ), pois pode haver confusões legítimas entre UTF-8 e ASCII sem uma marca de ordem de bytes . Infelizmente, iconvnão precede uma marca de ordem de bytes quando você especifica explicitamente um endianness ( UTF-16BEou UTF-16LE), portanto, precisamos usar o UTF-16que usa endianness específico da plataforma e depois file --mime-encodingdescobrir a verdadeira endianness iconvusada.

(Eu coloco todas as minhas codificações em maiúsculas, porque quando você lista todas iconvas codificações suportadas com iconv -ltodas elas estão em maiúsculas.)

# Find out MY_FILE's encoding
# We'll convert back to this at the end
FILE_ENCODING="$( file --brief --mime-encoding MY_FILE )"
# Find out bash's encoding, with which we should encode
# MY_FILE so sed doesn't fail with 
# sed: RE error: illegal byte sequence
BASH_ENCODING="$( locale charmap | tr [:lower:] [:upper:] )"
# Convert to UTF-16 (unknown endianness) so iconv ensures
# we have a byte-order mark
iconv -f "$FILE_ENCODING" -t UTF-16 MY_FILE > MY_FILE.utf16_encoding
# Whether we're using UTF-16BE or UTF-16LE
UTF16_ENCODING="$( file --brief --mime-encoding MY_FILE.utf16_encoding )"
# Now we can use MY_FILE.bash_encoding with sed
iconv -f "$UTF16_ENCODING" -t "$BASH_ENCODING" MY_FILE.utf16_encoding > MY_FILE.bash_encoding
# sed!
sed 's/.*/&/' MY_FILE.bash_encoding > MY_FILE_SEDDED.bash_encoding
# now convert MY_FILE_SEDDED.bash_encoding back to its original encoding
iconv -f "$BASH_ENCODING" -t "$FILE_ENCODING" MY_FILE_SEDDED.bash_encoding > MY_FILE_SEDDED
# Now MY_FILE_SEDDED has been processed by sed, and is in the same encoding as MY_FILE
Heath Borders
fonte
1
++ para técnicas úteis, especialmente file -b --mime-encodingpara descobrir e reportar a codificação de um arquivo. Há alguns aspectos que vale a pena abordar, no entanto, que farei em comentários separados.
precisa saber é o seguinte
2
Eu acho que é seguro dizer que o mundo Unix adotou o UTF-8 neste momento: o LC_CTYPEvalor padrão é geralmente <lang_region>.UTF-8, então qualquer arquivo sem uma BOM (marca de ordem de bytes) é, portanto, interpretado como um arquivo UTF-8. É apenas no mundo Windows que a pseudo-BOM 0xef 0xbb 0xff é usada; por definição, o UTF-8 não precisa de uma lista técnica e não é recomendado (como você declara); fora do mundo do Windows, essa pseudo-lista técnica faz com que as coisas quebrem .
precisa saber é o seguinte
2
Re Unfortunately, iconv doesn't prepend a byte-order mark when you explicitly specify an endianness (UTF-16BE or UTF-16LE): isso é por design: se você especificar o endianness explicitamente , não haverá necessidade de refleti-lo também por meio de uma lista técnica, portanto, nenhum será adicionado.
precisa saber é o seguinte
1
Re LC_*/ LANGvariáveis: bash, ksh, e zsh(possivelmente outros, mas não dash ) fazer respeitar a codificação de caracteres; verifique em shells do tipo POSIX com um código de idioma baseado em UTF-8 com v='ä'; echo "${#v}": um shell compatível com UTF-8 deve relatar 1; ou seja, ele deve reconhecer a sequência de bytes múltiplos ä( 0xc3 0xa4), como um único caractere. Talvez ainda mais importante, porém: os utilitários padrão ( sed, awk, cut, ...) também precisam ser locale / codificação-aware, e enquanto a maioria deles na moderna Unix-like plataformas são, há exceções, como awkno OSX, e cutno Linux.
mklement0
1
É louvável filereconhecer a pseudo-BOM UTF-8, mas o problema é que a maioria dos utilitários Unix que processam arquivos não o fazem , e geralmente quebram ou pelo menos se comportam mal quando confrontados com um. Sem uma BOM, fileidentifica corretamente um arquivo de bytes de 7 bits como ASCII e um que possui caracteres de vários bytes UTF-8 válidos como UTF-8. A beleza do UTF-8 é que ele é um superconjunto do ASCII: qualquer arquivo ASCII válido é, por definição, um arquivo UTF-8 válido (mas não vice-versa); é perfeitamente para seguro para tratar um arquivo ASCII como UTF-8 (o que tecnicamente é, ele só acontece de não contêm caracteres multi-byte.)
mklement0
2

Você simplesmente precisa canalizar um comando iconv antes do comando sed . Ex com entrada file.txt:

iconv -f ISO-8859-1 -t arquivo UTF8-MAC.txt | sed / algo / àéèêçùû / g '| .....

A opção -f é o conjunto de códigos 'from' e a opção -t é a conversão do conjunto de códigos 'to'.

Cuidado, as páginas da Web geralmente mostram letras minúsculas assim <charset = iso-8859-1 "/> e iconv usa letras maiúsculas. Você tem uma lista de conjuntos de códigos suportados por iconv em seu sistema com o comando iconv -l

UTF8-MAC é um conjunto de códigos OS Mac moderno para conversão.

Denis de Val Thorens
fonte
Veja também nomes de iconv e charset na lista de discussão iconv.
JWW
1

Alguém sabe como obter sed para imprimir a posição da sequência de bytes ilegal? Ou alguém sabe qual é a sequência ilegal de bytes?

$ uname -a
Darwin Adams-iMac 18.7.0 Darwin Kernel Version 18.7.0: Tue Aug 20 16:57:14 PDT 2019; root:xnu-4903.271.2~2/RELEASE_X86_64 x86_64

Eu fiz parte do caminho para responder às perguntas acima apenas usando tr .

Eu tenho um arquivo .csv que é um extrato do cartão de crédito e estou tentando importá-lo para o Gnucash. Como estou na Suíça, tenho que lidar com palavras como Zurique. Suspeitando que o Gnucash não goste "" nos campos numéricos, decido simplesmente substituir todos

; ;

com

;;

Aqui vai:

$ head -3 Auswertungen.csv | tail -1 | sed -e 's/; ;/;;/g'
sed: RE error: illegal byte sequence

Eu usei od para lançar alguma luz: Observe o 374 no meio dessa saída od -c

$ head -3 Auswertungen.csv | tail -1 | od -c
0000000    1   6   8   7       9   6   1   9       7   1   2   2   ;   5
0000020    4   6   8       8   7   X   X       X   X   X   X       2   6
0000040    6   0   ;   M   Y       N   A   M   E       I   S   X   ;   1
0000060    4   .   0   2   .   2   0   1   9   ;   9   5   5   2       -
0000100        M   i   t   a   r   b   e   i   t   e   r   r   e   s   t
0000120                Z 374   r   i   c   h                            
0000140    C   H   E   ;   R   e   s   t   a   u   r   a   n   t   s   ,
0000160        B   a   r   s   ;   6   .   2   0   ;   C   H   F   ;    
0000200    ;   C   H   F   ;   6   .   2   0   ;       ;   1   5   .   0
0000220    2   .   2   0   1   9  \n                                    
0000227

Então pensei em tentar convencer tr a substituir 374 pelo código de bytes correto. Então, primeiro, tentei algo simples, que não funcionou, mas teve o efeito colateral de me mostrar onde estava o byte problemático:

$ head -3 Auswertungen.csv | tail -1 | tr . .  ; echo
tr: Illegal byte sequence
1687 9619 7122;5468 87XX XXXX 2660;MY NAME ISX;14.02.2019;9552 - Mitarbeiterrest   Z

Você pode ver tr fianças no caractere 374.

O uso de perl parece evitar esse problema

$ head -3 Auswertungen.csv | tail -1 | perl -pne 's/; ;/;;/g'
1687 9619 7122;5468 87XX XXXX 2660;ADAM NEALIS;14.02.2019;9552 - Mitarbeiterrest   Z?rich       CHE;Restaurants, Bars;6.20;CHF;;CHF;6.20;;15.02.2019
Coxas mágicas
fonte
0

Minha solução alternativa estava usando o gnu sed. Funcionou bem para os meus propósitos.

lu_zero
fonte
De fato, o GNU sed é uma opção se você deseja ignorar bytes inválidos no fluxo de entrada (não há necessidade de LC_ALL=C sed ...solução alternativa), porque o GNU sedsimplesmente passa bytes inválidos ao invés de relatar um erro, mas observe que se você deseja reconhecer e processar adequadamente todos caracteres na sequência de entrada, não há como alterar a codificação da entrada primeiro (normalmente, com iconv).
usar o seguinte comando