Error de RE: secuencia de bytes ilegal en Mac OS X

184

Estoy tratando de reemplazar una cadena en un Makefile en Mac OS X para la compilación cruzada a iOS. La cadena tiene comillas dobles incrustadas. El comando es:

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

Y el error es:

sed: RE error: illegal byte sequence

He intentado escapar de las comillas dobles, comas, guiones y dos puntos sin alegría. Por ejemplo:

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

Me está costando mucho depurar el problema. ¿Alguien sabe cómo sedimprimir la posición de la secuencia de bytes ilegal? ¿O alguien sabe cuál es la secuencia ilegal de bytes?

jww
fuente
2
La secuencia de bytes ilegal suena como algo que obtienes al alimentar un ascii de 8 bits a algo que espera utf-8.
Klas Lindbäck
36
¿Puedes probar:LC_CTYPE=C && LANG=C && sed command
Anubhava
55
Gracias amigos. Era la LANGcosa. Suspiro ....
jww
3
@ user2719058: BSD sed(como también se usa en OS X) requiere -i ''(argumento de opción de cadena vacía separada) para la actualización in situ sin un archivo de copia de seguridad; con GNU sed, solo funciona -ipor sí solo - ver stackoverflow.com/a/40777793/45375
mklement0
1
Más uno para lo de LANG. Buena pena, eso es oscuro, no obvio y sorprendentemente difícil de investigar.
Spudley

Respuestas:

301

Un comando de muestra que muestra el síntoma: sed 's/./@/' <<<$'\xfc'falla, porque el byte 0xfcno es un carácter UTF-8 válido.
Tenga en cuenta que, por el contrario, GNU sed (Linux, pero también instalable en macOS) simplemente pasa el byte no válido, sin informar un error.

Usar la respuesta anteriormente aceptada es una opción si no le importa perder el soporte para su ubicación local verdadera (si está en un sistema de EE. UU. Y nunca necesita tratar con caracteres extranjeros, puede estar bien).

Sin embargo, el mismo efecto se puede conseguir ad-hoc para un solo comando solamente :

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

Nota: Lo que importa es una configuración efectiva LC_CTYPE de C, por LC_CTYPE=C sed ...lo que normalmente también funcionaría, pero si LC_ALLse establece (en algo diferente a C), anulará las LC_*variables de categoría individual como LC_CTYPE. Por lo tanto, el enfoque más robusto es establecer LC_ALL.

Sin embargo, (efectivamente) se configura LC_CTYPEpara Ctratar las cadenas como si cada byte fuera su propio carácter ( no se realiza ninguna interpretación basada en reglas de codificación), sin tener en cuenta la codificación UTF-8 - multibyte-on-demand - que OS X emplea de manera predeterminada , donde los caracteres extranjeros tienen codificaciones multibyte .

En pocas palabras: establecer LC_CTYPEenC hace que el shell y las utilidades solo reconozcan letras inglesas básicas como letras (las que están en el rango ASCII de 7 bits), por lo que los caracteres extranjeros. no se tratarán como letras , lo que hará que, por ejemplo, las conversiones en mayúsculas / minúsculas fallen.

Una vez más, esto puede estar bien si no necesita hacer coincidir caracteres codificados con varios bytes como é, y simplemente desea pasar dichos caracteres .

Si esto es insuficiente y / o desea comprender la causa del error original (incluida la determinación de qué bytes de entrada causaron el problema) y realizar conversiones de codificación a pedido, lea a continuación.


El problema es que la codificación del archivo de entrada no coincide con la del shell.
Más específicamente, el archivo de entrada contiene caracteres codificados de una manera que no es válida en UTF-8 (como dijo @Klas Lindbäck en un comentario): eso es lo que el sedmensaje de error intenta decir invalid byte sequence.

Lo más probable es que su archivo de entrada utilice una codificación de 8 bits de un solo byte , como la que ISO-8859-1se usa con frecuencia para codificar idiomas "europeos occidentales".

Ejemplo:

La letra acentuada àtiene un punto de código Unicode 0xE0(224), lo mismo que en ISO-8859-1. Sin embargo, debido a la naturaleza de la codificación UTF-8 , este único punto de código se representa como 2 bytes 0xC3 0xA0, mientras que intentar pasar el byte único no0xE0 es válido bajo UTF-8.

Aquí hay una demostración del problema usando la cadena voilàcodificada como ISO-8859-1, con la àrepresentada como un byte (a través de una cadena bash ( $'...') citada por ANSI-C que se usa \x{e0}para crear el byte):

Tenga en cuenta que el sedcomando es efectivamente un no-op que simplemente pasa la entrada, pero lo necesitamos para provocar el error:

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

Para simplemente ignorar el problema , LCTYPE=Cse puede usar el enfoque anterior :

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

Si desea determinar qué partes de la entrada causan el problema , intente lo siguiente:

  # 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}'

La salida le mostrará todos los bytes que tienen el conjunto de bits alto (bytes que exceden el rango ASCII de 7 bits) en forma hexadecimal. (Sin embargo, tenga en cuenta que eso también incluye secuencias multibyte UTF-8 codificadas correctamente: se necesitaría un enfoque más sofisticado para identificar específicamente bytes no válidos en UTF-8).


Realización de conversiones de codificación bajo demanda :

La utilidad estándar iconvse puede usar para convertir a ( -t) y / o desde ( -f) codificaciones; iconv -lenumera todos los compatibles.

Ejemplos:

Convierta FROM ISO-8859-1a la codificación vigente en el shell (basado en LC_CTYPE, que está UTF-8basado por defecto), basándose en el ejemplo anterior:

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

Tenga en cuenta que esta conversión le permite hacer coincidir correctamente los caracteres extranjeros :

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

Para convertir la entrada BACK a ISO-8859-1después del procesamiento, simplemente canalice el resultado a otro iconvcomando:

sed 's/à/ü/' <<<"$(iconv -f ISO-8859-1 <<<$'voil\x{e0}')" | iconv -t ISO-8859-1
mklement0
fuente
44
Yo diría que esta es una opción mucho mejor. Primero, no me gustaría perder el soporte multilingüe en todo Terminal. En segundo lugar, la respuesta aceptada se siente como una solución global a un problema local, algo que debe evitarse.
Alex
Tuve un par de pequeños ajustes a esto. Agradecería sus comentarios. stackoverflow.com/a/35046218/9636
Heath Borders el
LC_CTYPE=C sed 's/.*/&/' <<<$'voil\x{e0}'impresiones sed: RE error: illegal byte sequencepara mí en Sierra. echo $LC_ALLsalidas en_US.UTF-8FWIW.
ahcox
1
@ahcox: Sí, porque la configuración LC_ALL anula todas las demás LC_*variables, incluida LC_CTYPE, como se explica en la respuesta.
mklement0
2
@ mklement0 Genial, esto funciona: "LC_ALL = C sed 's /.*/&/' <<< $ 'voil \ x {e0}'". La precedencia se explica aquí para mis compañeros ignorantes ignorantes: pubs.opengroup.org/onlinepubs/7908799/xbd/envvar.html
ahcox
142

Agregue las siguientes líneas a su ~/.bash_profileo ~/.zshrcarchivo (s).

export LC_CTYPE=C 
export LANG=C
binarytemple_picsolve
fuente
29
en realidad funciona, pero ¿podría explicar por qué?
Hoang Pham
11
@HoangPham: la configuración LC_CTYPEen Chace que cada byte en cadenas sea su propio carácter sin aplicar ninguna regla de codificación. Dado que una violación de las reglas de codificación (UTF-8) causó el problema original, esto hace que el problema desaparezca. Sin embargo, el precio que paga es que el shell y las utilidades solo reconocen las letras inglesas básicas (las que están en el rango ASCII de 7 bits) como letras. Vea mi respuesta para más.
mklement0
66
Establecer esto permanentemente en los archivos de inicio de su shell deshabilitará muchos comportamientos útiles. Desea poner esto solo para comandos individuales que lo requieren absolutamente.
tripleee
44
Demasiado peligroso puede causar consecuencias inesperadas. Se podría usar LC_CTYPE=C sed …, es decir, solo en el comando sed.
Yongwei Wu
2
Esto deshabilitará por completo la compatibilidad con los caracteres Unicode en su shell. Adiós emojis, personajes de dibujo de líneas elegantes, letras con acentos, ... Mucho mejor configurarlo solo para el comando sed, como se describe en otras respuestas.
asmeurer
6

Mi solución había estado usando Perl:

find . -type f -print0 | xargs -0 perl -pi -e 's/was/now/g'
Vitaly Zdanevich
fuente
Este funciona muy bien. Y no he tenido errores al escapar de caracteres especiales a diferencia de los demás. Los anteriores me dieron problemas como "sed: error RE: secuencia de bytes ilegal" o sed: 1: "path_to_file": código de comando no válido.
JMags1632
3

La respuesta de mklement0 es genial, pero tengo algunos pequeños ajustes.

Parece una buena idea especificar explícitamente bashla codificación cuando se usaiconv . Además, deberíamos anteponer una marca de orden de bytes ( aunque el estándar Unicode no lo recomienda ) porque puede haber confusiones legítimas entre UTF-8 y ASCII sin una marca de orden de bytes . Desafortunadamente, iconvno antepone una marca de orden de bytes cuando especifica explícitamente una endianness ( UTF-16BEo UTF-16LE), por lo que debemos usar UTF-16, que usa endianness específico de la plataforma, y ​​luego usar file --mime-encodingpara descubrir la verdadera endianness iconvutilizada.

(En mayúscula todas mis codificaciones porque cuando enumeras todas iconv las codificaciones admitidas iconv -l, todas son mayú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
fuente
1
++ para técnicas útiles, especialmente file -b --mime-encodingpara descubrir y reportar la codificación de un archivo. Sin embargo, hay algunos aspectos que vale la pena abordar, que haré en comentarios separados.
mklement0
2
Creo que es seguro decir que el mundo de Unix ha adoptado UTF-8 en este punto: el LC_CTYPEvalor predeterminado suele ser <lang_region>.UTF-8, por lo que cualquier archivo sin BOM (marca de orden de bytes) se interpreta como un archivo UTF-8. Solo en el mundo de Windows se usa el pseudo-BOM 0xef 0xbb 0xff ; por definición, UTF-8 no necesita una lista de materiales y no se recomienda (como usted dice ); fuera del mundo de Windows, esta pseudo-BOM hace que las cosas se rompan .
mklement0
2
Re Unfortunately, iconv doesn't prepend a byte-order mark when you explicitly specify an endianness (UTF-16BE or UTF-16LE): eso es por diseño: si especificas el endianness explícitamente , no hay necesidad de reflejarlo también a través de una lista de materiales, por lo que no se agrega ninguno.
mklement0
1
Re LC_*/ LANGvariables: bash, ksh, y zsh(posiblemente otros, pero no dash ) hacer respetar la codificación de caracteres; verificar en shells tipo POSIX con un entorno local basado en UTF-8 con v='ä'; echo "${#v}": un shell con reconocimiento UTF-8 debe informar 1; es decir, debe reconocer la secuencia de varios bytes ä( 0xc3 0xa4), como un solo carácter. Tal vez aún más importante, sin embargo: las utilidades estándar ( sed, awk, cut, ...) también tienen que ser locale / codificación-consciente, y si bien la mayoría de ellos en la moderna Unix plataformas son, hay excepciones, como awken OSX, y cuten Linux.
mklement0
1
Es encomiable que filereconozca el pseudo-BOM UTF-8, pero el problema es que la mayoría de las utilidades de Unix que procesan el archivo no lo hacen , y generalmente se rompen o al menos se comportan mal cuando se enfrentan con uno. Sin una lista de materiales, fileidentifica correctamente un archivo de bytes de todos los 7 bits como ASCII, y uno que tiene caracteres válidos de múltiples bytes UTF-8 como UTF-8. La belleza de UTF-8 es que es un superconjunto de ASCII: cualquier archivo ASCII válido es, por definición, un archivo UTF-8 válido (pero no al revés); es perfectamente seguro tratar un archivo ASCII como UTF-8 (que técnicamente es, simplemente no contiene caracteres de varios bytes)
mklement0
2

Simplemente tiene que canalizar un comando iconv antes del comando sed . Ej con entrada file.txt:

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

-F opción es el conjunto de códigos 'desde' y la opción -t es la conversión del conjunto de códigos 'a'.

Tenga cuidado con las mayúsculas y minúsculas, las páginas web generalmente muestran minúsculas como <charset = iso-8859-1 "/> e iconv usa mayúsculas. Tiene una lista de conjuntos de códigos compatibles iconv en su sistema con el comando iconv -l

UTF8-MAC es un moderno conjunto de códigos OS Mac para la conversión.

Denis de Val Thorens
fuente
Consulte también los nombres de iconv y charset en la lista de correo de iconv.
jww
1

¿Alguien sabe cómo obtener sed para imprimir la posición de la secuencia de bytes ilegal? ¿O alguien sabe cuál es la secuencia 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

Obtuve parte del camino para responder lo anterior simplemente usando tr .

Tengo un archivo .csv que es un extracto de la tarjeta de crédito y estoy tratando de importarlo a Gnucash. Estoy basado en Suiza, así que tengo que lidiar con palabras como Zürich. Sospechando que a Gnucash no le gusta "" en los campos numéricos, decido simplemente reemplazar todos

; ;

con

;;

Aquí va:

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

Solía od para arrojar algo de luz: tenga en cuenta el 374 a la mitad de esta salida 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

Entonces pensé que podría tratar de persuadir a tr para que sustituya 374 por el código de byte correcto. Así que primero probé algo simple, que no funcionó, pero tuvo el efecto secundario de mostrarme dónde estaba el 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

Puedes ver tr fianzas en el carácter 374.

Usar perl parece evitar este 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
Muslos mágicos
fuente
0

Mi solución había estado usando GNU sed. Funcionó bien para mis propósitos.

lu_zero
fuente
De hecho, GNU sed es una opción si desea ignorar los bytes no válidos en la secuencia de entrada (sin necesidad de la LC_ALL=C sed ...solución alternativa), porque GNU sedsimplemente pasa bytes no válidos en lugar de informar un error, pero tenga en cuenta que si desea reconocer y procesar correctamente todos caracteres en la cadena de entrada, no hay forma de cambiar primero la codificación de la entrada (normalmente, con iconv).
mklement0