Les jeux de caractères sous Linux

Retour à la page Systèmes

Motivation

Souvent le terminal n'affiche pas les caractères attendus.
Cette page présente quelques recettes (non-exhaustives!) pour traquer la source du problème et tenter d'en isoler les acteurs.
Les caractères non-ascii qu'elle contient s'afficheront bien sur tout navigateur internet acceptant l'UTF8 (i.e. tout navigateur récent).

Diagnostic

On peut commencer par tester un cas de base avec un minimun d'intervenants:

  • Sortir de tout programme en train de tourner, pour ne plus avoir qu'un shell.
  • Si le problème disparaît à ce stade, il vient sans doute d'un programme particulier;
  • Exécuter «locale charmap» pour savoir ce que le shell croit devoir utiliser comme codage de caractères
  • Dans le terminal, choisir une option d'affichage semblable au résultat de «locale charmap» . L'option en question dépend du logiciel qui sert de terminal. Par exemple:
    • terminal GNOME 3.20: «Terminal --> Set character encoding»
    • client SSH Putty: «Settings->Window->Translation : Remote character set»

Si les manipulations précédentes ne résolvent pas le problème, chercher la nature des caractères indésirables:

  • exécuter la commande produisant les caractères inattendus, et la rediriger dans un fichier.
  • La commande «cat fichier» doit donner les mêmes caractères
  • La commande «file fichier» indiquera sans doute le type de caractères: par exemple «UTF-8 Unicode text» ou «ISO-8859 text».

On peut souvent reconnaître pifométriquement le type de problème (s'affichera bien en UTF-8):

  • «Ã©» : arrive pour un terminal latin-1 à qui on fournit le caractère «é» en UTF-8
  • «�»: un terminal UTF-8 a reçu une valeur invalide dans ce standard (par exemple un octet non-ascii suivi d'un octet ascii) (source: fileformat.info);
  • «⯑»: si l'interprétation UTF-8 est possible mais ambigüe;
  • «□» ou «⬜»:Carré avec 4 caractère hexadécimaux dedans: affichage UTF-8, numéro de caractère a priori valide, mais inconnu de la police de caractères courante. Le système prend alors une police par défaut (souvent Unicode BMP Fallback)

Conversion de caractères

Linux propose la commande «iconv» pour lire des caractères et les convertir d'un jeu de caractères à un autre.

Ainsi, pour afficher un fichier codé en latin-1 dans un terminal réglé pour UTF-8:

iconv <fichier-lat1 >fichier-utf8         #Conversion de fichier
cat fichier-lat1 | iconv -f ISO-8859-1 -t UTF-8 #Conversion juste pour l'affichage
Comme les «pipes» de Linux n'altèrent pas les octets transmis, on peut récupérer leur valeur hexa à la sortie.
Ainsi, pour voir le numéro unicode d'un caractère � copié-collé dans un terminal UTF-8:
echo -n � | iconv -f utf-8 -t utf-32be | hexdump -C
00000000  00 00 ff fd                                       |....|

Explication: le echo -n n'ajoute pas de caractère superflu comme un retour à la ligne, le codage utf-32be est pratiquement le numéro unicode du caractère sur 32 bits (le «be» pour «big endian» met les octets par poids décroissant), et le hexdump traduit ces 32 bits en chiffres hexadécimaux: 0xFFFD.

Le codage UTF-8

Le terme UTF-8 revenant souvent à propos des chaînes de caractères, un peu de vulgarisation ne devrait pas faire de mal. Des explications plus poussées sont dans «man utf-8», «man iconv», «man charsets», et «man unicode»:

  • Chaque caractère était, historiquement, codé sur 1 octet, i.e. sur 8 bits, ce qui permettait 256 caractères;
  • Pour des raisons techniques (la fiabilité des transmissions), le standard ASCII utilisait les 128 premiers caractères seulement;
  • Cela suffisait pour l'anglais mais, la fiabilité augmentant, les 128 autres codes sont devenus utilisables, et les standards s’en sont servi de façon distincte selon la langue (latin-1, latin-15, synonymes de ISO-8859-1, ISO-8859-15, ...);
  • Les langues à plus de 256 caractères (Chinois, Japonais, ...) ont créé des standards à 2 ou 4 octets par caractères;

Le standard UNICODE donne (en simplifiant) un numéro à chaque caractère de chaque langue. Sans imposer de codage particulier, il reprend, pour la compatibilité, les 128 numéros ASCII (beaucoup d'explications sont ici );

Arrive la question du codage: les numéros UNICODE prennent souvent 2 voire 4 octets en binaire; mais utiliser 2 ou 4 octets par caractère dans des langues où les caractères ASCII dominent serait du gaspillage.

D'où l'idée du codage UTF-8 (Unicode Transformation Format): il écrit les numéros ASCII 0-127 sur un seul octet, et les suivants (>=128) à l'aide d'octets de «prolongation». Voici ce qui a été retenu:

  • Octets 0xxxxxxx (binaire): les caractères ASCII 0 à 127;
  • Octets 11xxxxxx (binaire): Début de séquence UTF8: le nombre de bits 1 avant le premier bit 0 donne la longueur de la séquence en octets; les bits d’après sont les premiers de la séquence;
  • Octets 10xxxxxx (binaire): octets de "prolongation", donnant 6 bits de la valeur à coder;
  • Une valeur donnée doit être codée par la plus courte séquence possible;

Ce codage s’est imposé rapidement par ses bonnes qualités techniques et pratiques, dont en particulier:

  • un terminal UTF-8 affiche parfaitement les fichiers ASCII; réciproquement, un terminal ASCII (ou ASCII étendu comme latin-1) ne maltraite que les caractères non-ASCII, souvent très minoritaires;
  • il empêche de confondre un octet de prolongation, un octet de début de séquence ou un octet codant un caractère ASCII;
  • la longueur d'une séquence est connue dès son premier octet.
  • les fichiers grossissent peu, car les langages occidentaux ont une majorité de caractères ASCII (codés sur 8 bits), les autres caractères tenant sur deux octets en UTF-8;
  • les algorithmes omniprésents implémentés pour 8 bits, comme lire une ligne entière ou chercher une chaîne de caractères, restent parfaitement valables;

L'inconvénient principal est que la longueur d'une chaîne, en caractères, n'est plus synonyme de sa taille en octets: il faut décoder pour la connaître.

Pour aller plus loin

L'article pourrait se prolonger indéfiniment. Quelques guides complémentaires (en anglais):