Erreur RE: séquence d'octets illégale sous Mac OS X

196

J'essaie de remplacer une chaîne dans un Makefile sur Mac OS X pour une compilation croisée vers iOS. La chaîne contient des guillemets doubles. La commande est:

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

Et l'erreur est:

sed: RE error: illegal byte sequence

J'ai essayé d'échapper aux guillemets, virgules, tirets et deux-points sans joie. Par exemple:

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

Je passe beaucoup de temps à déboguer le problème. Quelqu'un sait-il comment sedimprimer la position de la séquence d'octets illégale? Ou est-ce que quelqu'un sait quelle est la séquence d'octets illégale?

jww
la source
3
Une séquence d'octets illégale ressemble à quelque chose que vous obtenez lorsque vous alimentez un ascii 8 bits à quelque chose qui attend utf-8.
Klas Lindbäck
41
Pouvez-vous essayer:LC_CTYPE=C && LANG=C && sed command
anubhava
6
Merci à tous. C'était la LANGchose. Soupir ....
jww
4
@ user2719058: BSD sed(tel qu'utilisé également sur OS X) requiert -i ''(argument d'option de chaîne vide distinct) pour la mise à jour sur place sans fichier de sauvegarde; avec GNU sed, ne -ifonctionne que par lui-même - voir stackoverflow.com/a/40777793/45375
mklement0
2
Plus un pour le truc LANG. Bon chagrin, c'est obscur, non évident et étonnamment difficile à rechercher.
Spudley

Réponses:

310

Un exemple de commande qui présente le symptôme: sed 's/./@/' <<<$'\xfc'échoue, car l'octet 0xfcn'est pas un caractère UTF-8 valide.
Notez que, en revanche, GNU sed (Linux, mais également installable sur macOS) passe simplement l'octet invalide, sans signaler d'erreur.

L'utilisation de la réponse précédemment acceptée est une option si cela ne vous dérange pas de perdre la prise en charge de votre véritable locale (si vous êtes sur un système américain et que vous n'avez jamais besoin de gérer des caractères étrangers, cela peut être bien.)

Cependant, le même effet peut être obtenu ad hoc pour une seule commande uniquement :

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

Remarque: Ce qui compte, c'est un réglage efficace LC_CTYPE de C, donc LC_CTYPE=C sed ...cela fonctionnerait normalement aussi, mais s'il LC_ALLse trouve être défini (sur autre chose que C), il remplacera les LC_*variables de catégorie individuelles telles que LC_CTYPE. Ainsi, l'approche la plus robuste est de définir LC_ALL.

Cependant, le réglage (effectivement) LC_CTYPEsur Ctraite les chaînes comme si chaque octet était son propre caractère ( aucune interprétation basée sur des règles de codage n'est effectuée), sans égard pour le codage UTF-8 multi-octets à la demande qu'OS X utilise par défaut , où les caractères étrangers ont des encodages multi - octets .

En un mot: le réglage LC_CTYPEsurC fait que le shell et les utilitaires ne reconnaissent que les lettres anglaises de base comme des lettres (celles de la plage ASCII 7 bits), de sorte que les caractères étrangers. ne seront pas traités comme des lettres , ce qui entraînera, par exemple, l'échec des conversions majuscules / minuscules.

Encore une fois, cela peut convenir si vous n'avez pas besoin de faire correspondre des caractères codés sur plusieurs octets tels que é, et que vous souhaitez simplement passer ces caractères .

Si cela est insuffisant et / ou si vous souhaitez comprendre la cause de l'erreur d'origine (y compris déterminer quels octets d'entrée ont causé le problème) et effectuer des conversions de codage à la demande, lisez la suite ci-dessous.


Le problème est que le codage du fichier d'entrée ne correspond pas à celui du shell.
Plus précisément, le fichier d'entrée contient des caractères encodés d'une manière qui n'est pas valide en UTF-8 (comme @Klas Lindbäck l'a déclaré dans un commentaire) - c'est ce que le sedmessage d'erreur essaie de dire invalid byte sequence.

Très probablement, votre fichier d'entrée utilise un codage 8 bits à un octet tel que ISO-8859-1, fréquemment utilisé pour coder les langues «d'Europe occidentale».

Exemple:

La lettre accentuée àa un point de code Unicode 0xE0(224) - le même que dans ISO-8859-1. Cependant, en raison de la nature du codage UTF-8 , ce point de code unique est représenté par 2 octets - 0xC3 0xA0, alors que tenter de passer l' octet unique 0xE0 est invalide sous UTF-8.

Voici une démonstration du problème en utilisant la chaîne voilàcodée comme ISO-8859-1, avec le àreprésenté comme un octet (via une chaîne bash entre guillemets ANSI-C ( $'...') qui utilise \x{e0}pour créer l'octet):

Notez que la sedcommande est effectivement un no-op qui passe simplement l'entrée, mais nous en avons besoin pour provoquer l'erreur:

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

Pour ignorer simplement le problème , l' LCTYPE=Capproche ci-dessus peut être utilisée:

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

Si vous souhaitez déterminer quelles parties de l'entrée sont à l'origine du problème , essayez ce qui suit:

  # 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 sortie vous montrera tous les octets qui ont le bit haut défini (octets qui dépassent la plage ASCII 7 bits) sous forme hexadécimale. (Notez, cependant, que cela inclut également les séquences multioctets UTF-8 correctement codées - une approche plus sophistiquée serait nécessaire pour identifier spécifiquement les octets non valides en UTF-8.)


Effectuer des conversions d'encodage à la demande :

L'utilitaire standard iconvpeut être utilisé pour convertir des encodages en ( -t) et / ou à partir de ( -f); iconv -lrépertorie tous ceux pris en charge.

Exemples:

Convertissez FROM ISO-8859-1en encodage en vigueur dans le shell (basé sur LC_CTYPE, qui est UTF-8basé par défaut), en vous basant sur l'exemple ci-dessus:

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

Notez que cette conversion vous permet de faire correspondre correctement les caractères étrangers :

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

Pour convertir l'entrée BACK en ISO-8859-1après traitement, dirigez simplement le résultat vers une autre iconvcommande:

sed 's/à/ü/' <<<"$(iconv -f ISO-8859-1 <<<$'voil\x{e0}')" | iconv -t ISO-8859-1
mklement0
la source
4
Je dirais que c'est une bien meilleure option. Premièrement, je ne voudrais pas perdre le support multilingue dans tout le terminal. Deuxièmement, la réponse acceptée ressemble à une solution globale à un problème local - quelque chose à éviter.
Alex
J'ai eu quelques petits ajustements à cela. J'apprécierais vos commentaires. stackoverflow.com/a/35046218/9636
Heath Borders
LC_CTYPE=C sed 's/.*/&/' <<<$'voil\x{e0}'imprime sed: RE error: illegal byte sequencepour moi sur Sierra. echo $LC_ALLsorties en_US.UTF-8FWIW.
ahcox
1
@ahcox: Oui, car le paramètre LC_ALL remplace toutes les autres LC_*variables, y compris LC_CTYPE, comme expliqué dans la réponse.
mklement0
2
@ mklement0 Cool, cela fonctionne: "LC_ALL = C sed 's /.*/&/' <<< $ 'voil \ x {e0}'". La préséance expliquée ici pour mes camarades ignorants inattentifs: pubs.opengroup.org/onlinepubs/7908799/xbd/envvar.html
ahcox
147

Ajoutez les lignes suivantes à vos fichiers ~/.bash_profileou ~/.zshrc.

export LC_CTYPE=C 
export LANG=C
binarytemple_picsolve
la source
31
cela fonctionne réellement, mais pouvez-vous expliquer pourquoi?
Hoang Pham le
11
@HoangPham: définir LC_CTYPEsur Cfait que chaque octet des chaînes est son propre caractère sans appliquer de règles de codage. Étant donné qu'une violation des règles de codage (UTF-8) a provoqué le problème d'origine, le problème disparaît. Cependant, le prix à payer est que le shell et les utilitaires ne reconnaissent alors que les lettres anglaises de base (celles de la gamme ASCII 7 bits) comme des lettres. Voir ma réponse pour plus.
mklement0
6
Définir ceci de façon permanente dans les fichiers de démarrage de votre shell désactivera de nombreux comportements utiles. Vous voulez le mettre uniquement pour les commandes individuelles qui en ont absolument besoin.
tripleee
4
Trop dangereux peut entraîner des conséquences inattendues. On pourrait utiliser LC_CTYPE=C sed …, c'est à dire uniquement sur la commande sed.
Yongwei Wu
2
Cela désactivera complètement la prise en charge des caractères Unicode dans votre shell. Adieu les emojis, les caractères de dessin au trait fantaisie, les lettres avec des accents, .... Il vaut mieux définir cela uniquement pour la commande sed, comme décrit dans d'autres réponses.
asmeurer
8

Ma solution de contournement utilisait Perl:

find . -type f -print0 | xargs -0 perl -pi -e 's/was/now/g'
Vitaly Zdanevich
la source
1
Celui-ci fonctionne très bien. Et je n'ai eu aucune erreur en échappant des caractères spéciaux contrairement aux autres. Les précédents m'ont donné des problèmes comme "sed: erreur RE: séquence d'octets illégale" ou sed: 1: "path_to_file": code de commande invalide.
JMags1632
3

La réponse de mklement0 est excellente, mais j'ai quelques petits ajustements.

Il semble judicieux de spécifier explicitement bashle codage de lors de l 'utilisation iconv. De plus, nous devrions ajouter une marque d'ordre d'octet ( même si le standard Unicode ne le recommande pas ) car il peut y avoir des confusions légitimes entre UTF-8 et ASCII sans marque d'ordre d'octet . Malheureusement, iconvne ajoute pas de marque d'ordre d'octet lorsque vous spécifiez explicitement une endianness ( UTF-16BEou UTF-16LE), nous devons donc utiliser UTF-16, qui utilise l'endianness spécifique à la plate-forme, puis l'utiliser file --mime-encodingpour découvrir la vraie endianness iconvutilisée.

(Je mets tous mes encodages en majuscules car lorsque vous listez tous iconvles encodages pris en charge par, iconv -lils sont tous en majuscules.)

# 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
Frontières de la santé
la source
1
++ pour des techniques utiles, en particulier file -b --mime-encodingpour découvrir et signaler l'encodage d'un fichier. Cependant, certains aspects méritent d'être abordés, ce que je ferai dans des commentaires séparés.
mklement0
2
Je pense qu'il est prudent de dire que le monde Unix a adopté UTF-8 à ce stade: la LC_CTYPEvaleur par défaut est généralement <lang_region>.UTF-8, donc tout fichier sans BOM (marque d'ordre des octets) est donc interprété comme un fichier UTF-8. Ce n'est que dans le monde Windows que la pseudo-nomenclature 0xef 0xbb 0xff est utilisée; par définition, UTF-8 n'a pas besoin d' une nomenclature et n'est pas recommandé (comme vous le dites); en dehors du monde Windows, cette pseudo-nomenclature provoque des pannes .
mklement0
2
Re Unfortunately, iconv doesn't prepend a byte-order mark when you explicitly specify an endianness (UTF-16BE or UTF-16LE): c'est par conception: si vous spécifiez explicitement l'endianness , il n'est pas nécessaire de la refléter également via une nomenclature, donc aucune n'est ajoutée.
mklement0
1
Re LC_*/ LANGvariables: bash, ksh, et zsh(peut - être d' autres, mais pas dash ) faire respecter le codage de caractères; vérifier dans des shells de type POSIX avec une locale basée sur v='ä'; echo "${#v}"UTF-8 avec : un shell compatible UTF-8 devrait signaler 1; c'est-à-dire qu'il doit reconnaître la séquence multi-octets ä( 0xc3 0xa4), comme un seul caractère. Peut-être plus important encore, cependant: les utilitaires standards ( sed, awk, cut, ...) doivent également être locale / encodage-courant, et alors que la plupart d'entre eux sur des plates - formes modernes de type Unix sont, il y a des exceptions, comme awksur Mac OS X, et cutsur Linux.
mklement0
1
Il est louable de filereconnaître la pseudo-nomenclature UTF-8, mais le problème est que la plupart des utilitaires Unix qui traitent des fichiers ne le font pas et se cassent généralement ou se comportent du moins mal lorsqu'ils sont confrontés à un fichier . Sans nomenclature, fileidentifie correctement un fichier d'octets de 7 bits comme ASCII et un fichier qui a des caractères multi-octets UTF-8 valides comme UTF-8. La beauté de l'UTF-8 est qu'il s'agit d'un sur - ensemble d'ASCII: tout fichier ASCII valide est par définition un fichier UTF-8 valide (mais pas l'inverse); il est parfaitement prudent de traiter un fichier ASCII comme UTF-8 (ce qui est techniquement, il se trouve qu'il ne contient aucun caractère multi-octets.)
mklement0
2

Vous devez simplement diriger une commande iconv avant la commande sed . Ex avec entrée file.txt:

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

L' option -f est le jeu de codes 'from' et l'option -t est la conversion du jeu de codes 'vers'.

Faites attention à la casse, les pages Web affichent généralement des minuscules comme ça <charset = iso-8859-1 "/> et iconv utilise des majuscules. Vous avez une liste des jeux de codes pris en charge par iconv dans votre système avec la commande iconv -l

UTF8-MAC est un jeu de codes OS Mac moderne pour la conversion.

Denis de Val Thorens
la source
Voir également les noms iconv et charset sur la liste de diffusion iconv.
jww
1

Est-ce que quelqu'un sait comment obtenir sed pour afficher la position de la séquence d'octets illégale? Ou est-ce que quelqu'un sait quelle est la séquence d'octets illégale?

$ 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

J'ai fait une partie du chemin pour répondre à ce qui précède simplement en utilisant tr .

J'ai un fichier .csv qui est un relevé de carte de crédit et j'essaye de l'importer dans Gnucash. Je suis basé en Suisse et je dois donc composer avec des mots comme Zürich. Suspectant que Gnucash n'aime pas "" dans les champs numériques, je décide de tout simplement remplacer

; ;

avec

;;

Voici:

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

J'ai utilisé od pour faire la lumière: notez le 374 à mi-chemin de cette sortie 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

Ensuite, j'ai pensé que je pourrais essayer de persuader tr de remplacer 374 par quel que soit le code d'octet correct. Alors j'ai d'abord essayé quelque chose de simple, qui n'a pas fonctionné, mais qui a eu pour effet secondaire de me montrer où se trouvait l'octet gênant:

$ 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

Vous pouvez voir tr bails au caractère 374.

L'utilisation de perl semble éviter ce problème

$ 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
Cuisses magiques
la source
0

Ma solution de contournement utilisait gnu sed. A bien fonctionné pour mes besoins.

lu_zero
la source
En effet, GNU sed est une option si vous voulez ignorer les octets invalides dans le flux d'entrée (pas besoin de la LC_ALL=C sed ...solution de contournement), car GNU passesed simplement les octets invalides au lieu de signaler une erreur, mais notez que si vous voulez bien reconnaître et traiter tous caractères dans la chaîne d'entrée, il n'y a aucun moyen de modifier d'abord le codage de l'entrée (généralement, avec iconv).
mklement0