Twitter iPhone pliant OnePlus 11 PS5 Disney+ Orange Livebox Windows 11

Support Unicode (UTF-8) et locales

24 réponses
Avatar
Vincent Lefevre
Il y a pas mal de doc sur Perl et Unicode, mais ça reste flou et le
comportement semble bizarre. Voici un exemple nommé "enctest":

#!/usr/bin/env perl
use strict;

my $agrave = chr(0xe0);
my $str = "A grave = '$agrave'";
print "stdout: $str\n";
print STDERR "stderr: $str\n";
warn " warn: $str\n";
die " die: $str\n";

Je voudrais obtenir un "à" encodé correctement, que ce soit avec des
locales en ISO-8859-1 ou avec des locales en UTF-8 (évidemment, le
terminal est configuré de manière cohérente avec les locales). Je me
demande ce que je dois ajouter à ce script (de manière la plus simple
possible).

Je passe sur le cas des locales en ISO-8859-1, qui ne posent pas
vraiment de problèmes. Je considère alors qu'on est en UTF-8.

Si j'exécute "./enctest" ou "perl enctest", ça sort en ISO-8859-1,
comme indiqué dans la doc. Je ne veux pas une solution basée sur
l'option -C, car je veux pouvoir exécuter le script comme ceci:
"./enctest". Il faut que la solution soit dans le script lui-même
(de manière aussi à ne pas affecter les autres scripts Perl, donc
pas de PERL_UNICODE non plus).

Cependant, l'option -C n'a pas l'air de bien fonctionner, donc je
vais tout de même mentionner ceci:

$ perl -C enctest
stdout: A grave = 'à'
stderr: A grave = 'à'
warn: A grave = '?'
die: A grave = '?'

Note: le '?' désigne ici un "à" encodé en ISO-8859-1, que le terminal
ne comprend donc pas. Pourquoi le warn et le die ne fonctionnent-ils
pas correctement?

Si j'utilise

use encoding ':locale';

alors j'obtiens sans l'option -C:

stdout: A grave = 'à'
stderr: A grave = '?'
warn: A grave = 'à'
die: A grave = 'à'

Un "man encoding" dit que STDERR n'est pas affecté par le use encoding,
c'est donc normal pour les deux premières lignes. Mais alors pourquoi
le warn et le die sont-ils maintenant en UTF-8???

Maintenant, si j'utilise seulement

use open OUT => ':locale';

alors j'ai le même résultat qu'avec -C (c'est cohérent). La solution
pour obtenir un "à" correctement encodé est d'utiliser les deux:

use encoding ':locale';
use open OUT => ':locale';

mais aucune idée du pourquoi...

Tout ceci est testé avec perl 5.8.7 sous Debian.

--
Vincent Lefèvre <vincent@vinc17.org> - Web: <http://www.vinc17.org/>
100% accessible validated (X)HTML - Blog: <http://www.vinc17.org/blog/>
Work: CR INRIA - computer arithmetic / SPACES project at LORIA

10 réponses

1 2 3
Avatar
Nicolas George
Vincent Lefevre wrote in message
<20051217001913$:
Maintenant, si j'utilise seulement

use open OUT => ':locale';

alors j'ai le même résultat qu'avec -C (c'est cohérent). La solution
pour obtenir un "à" correctement encodé est d'utiliser les deux:

use encoding ':locale';
use open OUT => ':locale';

mais aucune idée du pourquoi...


Pour faire simple :

Perl a deux notions de chaînes de caractèrs : les chaînes d'octets, et les
chaînes de caractèrs Unicode (que la doc désigne fautivement par UTF-8). Dès
qu'il y a des accents, il faut utiliser des chaînes de caractères, le
comportement des chaînes d'octets est le comportement plus ou moins
rétrocompatible et fantaisiste des anciennes versions.

Pour passer d'une chaîne d'octets à une chaîne de caractères Unicode, on
utilise Encode::decode, pour le contraire, on utilise Encode::encode.

Les chaînes écrites directement dans le code du programme sont des chaînes
d'octets par défaut. La directive « use utf8 » indique que le code source du
programme est en UTF-8, et les chaînes littérales deviennent alors des
chaînes de caractères Unicode en conséquence (on obtient en prime le droit
d'utiliser n'importe quel caractère de catégorie lettre dans un
identificateur, ça va être sympa pour lire des scripts écrits par des
chinois...).

Les chaînes lues sur des filehandles sont par défaut en octets, on peut les
passer en Unicode (encodé en UTF-8) en ajoutant le module d'entrée/sortie
:utf8, soit avec binmode soit à l'ouverture :

binmode STDIN, ":utf8";
open my $f, "<:utf8" , "some file";

Symétriquement, les filehandles en écriture sont par défaut prévus pour des
octets, et il se passe des trucs bizarres si on leur donne de l'Unicode,
mais on peut ajouter le module :utf8 pour changer ce comportement.

Si on veut travailler avec d'autres encodages qu'UTF-8 (c'est une mauvaise
idée, mais il faut bien interagir avec les trucs antédiluviens), on utilise
:encoding(iso-8859-1) plutôt que :utf8.

La manière correcte pour connaître le nom de l'encodage en fonction de la
locale est :

use I18N::Langinfo "langinfo", "CODESET";
my $encoding = langinfo CODESET;

La directive open permet d'ajouter implicitement des modules (par exemple
:encoding(foobar)) sur les fichiers à l'ouverture. La notation :locale dans
ce contexte fait la recherche de l'encodage avec langinfo (plus des
heuristiques si la config des locales est cassée) et met le bon module
:encoding.

La directive encoding fait comme la directive utf8, mais pour d'autres
encodages. C'est une mauvaise idée : pas de problèmes de compatibilité ici.
Elle autorise d'utiliser :locale, ce qui est encore plus mauvais (l'encodage
d'un code source ne change pas au gré d'une variable d'environnement). Elle
change au passage l'encodage de STDIN et STDOUT, ce qui peut se faire
autrement. Je recommande de ne pas l'utiliser.

Finalement, ce que je recommande :

* « use utf8 » et sauver le script en UTF-8.

* binmode ..., ":encoding(...)" ou open ..., "...:encoding(...)".

* langinfo CODESET pour déterminer l'encodage

Éventuellement, use open si on est certain de ne manipuler que des fichiers
tous dans le même encodage.

Tout ceci est testé avec perl 5.8.7 sous Debian.


Tout ceci est valable pour perl 5.8, la gestion d'Unicode est complètement
cassée dans 5.6 (et inexistante avant).

Avatar
Vincent Lefevre
Tout d'abord, merci pour ces infos.

Dans l'article <do1b10$9gb$,
Nicolas George <nicolas$ écrit:

La directive encoding fait comme la directive utf8, mais pour d'autres
encodages. C'est une mauvaise idée : pas de problèmes de compatibilité
ici.


Pourquoi une mauvaise idée?

Elle autorise d'utiliser :locale, ce qui est encore plus mauvais
(l'encodage d'un code source ne change pas au gré d'une variable
d'environnement).


Oui, ceci dit, si le code source est en ASCII, ce n'est pas vraiment
un problème.

Elle change au passage l'encodage de STDIN et STDOUT, ce qui peut se
faire autrement. Je recommande de ne pas l'utiliser.


OK.

Finalement, ce que je recommande :

* « use utf8 » et sauver le script en UTF-8.


Comme mon source est en ASCII, je ne vais pas l'utiliser. En fait,
j'ai des commentaires en ISO-8859-1, ce qui convient mieux à ma
config (j'attends le jour où "less" sera capable de reconnaître
l'encodage comme Emacs...).

* binmode ..., ":encoding(...)" ou open ..., "...:encoding(...)".

* langinfo CODESET pour déterminer l'encodage


OK. Mais après les modif, je me prends un segmentation fault à cause
d'une récursion infinie au niveau du "warn"! Je viens de faire un
rapport de bug sur le BTS de Debian. Donc pour le moment, ce n'est
pas la bonne solution.

--
Vincent Lefèvre - Web: <http://www.vinc17.org/>
100% accessible validated (X)HTML - Blog: <http://www.vinc17.org/blog/>
Work: CR INRIA - computer arithmetic / SPACES project at LORIA

Avatar
Nicolas George
Vincent Lefevre wrote in message
<20051218010915$:
Pourquoi une mauvaise idée?


Au niveau fonctionnalité, UTF-8 remplace avantageusement à peu près tous les
encodages nationaux. Au niveau programmation, il est plus facile de gérer
UTF-8 que de gérer toute la variété des encodages : il serait donc
souhaitable que les encodages nationaux disparaissent le plus vite possible
pour que les programmes au contact de texte puissent être simplifiés autant
que possible.

Comme mon source est en ASCII, je ne vais pas l'utiliser. En fait,
j'ai des commentaires en ISO-8859-1, ce qui convient mieux à ma
config (j'attends le jour où "less" sera capable de reconnaître
l'encodage comme Emacs...).


Personnellement, j'utilise de moins en moins less comme pager, et de plus en
plus vim.

OK. Mais après les modif, je me prends un segmentation fault à cause
d'une récursion infinie au niveau du "warn"! Je viens de faire un
rapport de bug sur le BTS de Debian. Donc pour le moment, ce n'est
pas la bonne solution.


C'est bizarre : j'utilise quotidiennement des scripts qui utilise cette
construction, sans problème. Tu as un exemple complet minimal ?

Avatar
Vincent Lefevre
Dans l'article <do2eq8$lav$,
Nicolas George <nicolas$ écrit:

Vincent Lefevre wrote in message
<20051218010915$:
Pourquoi une mauvaise idée?


Au niveau fonctionnalité, UTF-8 remplace avantageusement à peu près tous les
encodages nationaux. Au niveau programmation, il est plus facile de gérer
UTF-8 que de gérer toute la variété des encodages : il serait donc
souhaitable que les encodages nationaux disparaissent le plus vite possible
pour que les programmes au contact de texte puissent être simplifiés autant
que possible.


Le problème actuel est que tous les logiciels ne supportent pas encore
UTF-8 (car multi-byte).

Personnellement, j'utilise de moins en moins less comme pager, et de
plus en plus vim.


Ça n'a pas l'air de bien marcher:

$ PAGER=vim man vim
Reformatting vim(1), please wait...
Vim: Warning: Input is not from a terminal
Vim: Error reading input, exiting...
Vim: preserving files...
Vim: Finished.
man: command exited with status 256: /usr/bin/zsoelim /tmp/zmanocmcTf | /usr/bin/tbl | /usr/bin/nroff -mandoc -Tlatin1 | vim

C'est bizarre : j'utilise quotidiennement des scripts qui utilise cette
construction, sans problème. Tu as un exemple complet minimal ?


#!/usr/bin/perl

use strict;
use I18N::Langinfo qw(langinfo CODESET);

my $encoding = langinfo CODESET;
binmode STDOUT, ":encoding($encoding)";
binmode STDERR, ":encoding($encoding)";
print "Encoding: $encodingn";

my $agrave = chr(0xe0);
my $str = "A grave = '$agrave'";
warn " warn: $strn";

que j'ai donné sur http://bugs.debian.org/cgi-bin/bugreport.cgi?bug43831

--
Vincent Lefèvre - Web: <http://www.vinc17.org/>
100% accessible validated (X)HTML - Blog: <http://www.vinc17.org/blog/>
Work: CR INRIA - computer arithmetic / SPACES project at LORIA


Avatar
Nicolas George
Vincent Lefevre wrote in message
<20051218105429$:
Le problème actuel est que tous les logiciels ne supportent pas encore
UTF-8 (car multi-byte).


C'est pourquoi il faut autant que possible gérer les encodages quand on
dialogue avec l'extérieur. Mais quand on n'a pas cette contrainte, ce qui
est précisément le cas ici, il ne faut surtout pas utiliser autre chose
qu'UTF-8.

Ça n'a pas l'air de bien marcher:

$ PAGER=vim man vim


PAGER="vim -"

Mais ça ne gère pas les séquences d'échappement utilisées par man. Ceci dit,
man adapte son encodage de sortie à la locale, donc ce n'est pas un problème
ici.

binmode STDERR, ":encoding($encoding)";


En n'encodant pas STDERR (ce qui est raisonnable : STDERR est le dernier
recours, il ne faut donc pas la surcharger ; d'ailleurs les modules
standards de perl n'encodent jamais STDERR) ça marche.

Avatar
Vincent Lefevre
Dans l'article <do3sg1$13dk$,
Nicolas George <nicolas$ écrit:

Vincent Lefevre wrote in message
<20051218105429$:
Le problème actuel est que tous les logiciels ne supportent pas encore
UTF-8 (car multi-byte).


C'est pourquoi il faut autant que possible gérer les encodages quand on
dialogue avec l'extérieur. Mais quand on n'a pas cette contrainte, ce qui
est précisément le cas ici, il ne faut surtout pas utiliser autre chose
qu'UTF-8.


Si, justement ici j'ai cette contrainte: il faut sortir les messages
dans l'encodage utilisé par le terminal, i.e. celui indiqué par les
locales.

Ça n'a pas l'air de bien marcher:

$ PAGER=vim man vim


PAGER="vim -"

Mais ça ne gère pas les séquences d'échappement utilisées par man.
Ceci dit, man adapte son encodage de sortie à la locale, donc ce
n'est pas un problème ici.


Ici ça donne un truc illisible:

N^HNA^HAM^HME^HE
vim - Vi IMproved, a programmers text editor

S^HSY^HYN^HNO^HOP^HPS^HSI^HIS^HS
v^Hvi^Him^Hm [options] [file ..]
v^Hvi^Him^Hm [options] -
[...]

binmode STDERR, ":encoding($encoding)";


En n'encodant pas STDERR (ce qui est raisonnable : STDERR est le dernier
recours, il ne faut donc pas la surcharger ; d'ailleurs les modules
standards de perl n'encodent jamais STDERR) ça marche.


Évidemment qu'il faut encoder STDERR. Mais les modules n'ont
probablement pas besoin de le faire, car ils sortent tout en
anglais. Essaie par exemple le programme suivant (où STDERR
n'est pas encodé) dans des locales UTF8:

#!/usr/bin/env perl

use strict;
use I18N::Langinfo qw(langinfo CODESET);

my $encoding = langinfo CODESET;
binmode STDOUT, ":encoding($encoding)";
print "Encoding: $encodingn";

my $agrave = chr(0xe0);
my $str = "A grave = '$agrave'";
print "STDOUT: $strn";
print STDERR "STDERR: $strn";

Ça s'affiche comme ceci:

Encoding: UTF-8
STDOUT: A grave = 'à'
STDERR: A grave = '?'

D'une manière générale, si stderr n'était pas encodé, une commande
du style "LANG=fr_FR cp" imprimerait un message pas complètement
lisible (le message en question est "cp: opérande fichier manquant"
et a donc un caractère accentué).

--
Vincent Lefèvre - Web: <http://www.vinc17.org/>
100% accessible validated (X)HTML - Blog: <http://www.vinc17.org/blog/>
Work: CR INRIA - computer arithmetic / SPACES project at LORIA


Avatar
Nicolas George
Vincent Lefevre wrote in message
<20051218183459$:
Si, justement ici j'ai cette contrainte: il faut sortir les messages
dans l'encodage utilisé par le terminal, i.e. celui indiqué par les
locales.


Il était question de l'encodage du script lui-même, pas de l'encodage des
messages qu'il affiche. Ces deux notions sont tout à fait indépendantes,
justement.

Mais ça ne gère pas les séquences d'échappement utilisées par man.
Ici ça donne un truc illisible:



C'est ce que j'ai marqué juste au dessus.

Évidemment qu'il faut encoder STDERR. Mais les modules n'ont
probablement pas besoin de le faire, car ils sortent tout en
anglais.


Ce qui est une très bonne chose : la traduction des messages d'erreur est
une connerie qui cause des pertes de temps sans fin sur les groupes de
discussion d'entraide.

Il ne faut pas encoder STDERR, et il faut faire attention à la garder en
ASCII, de manière à ce que les informations données soient lisibles au
maximum en toutes circonstances, même quand la moitié de l'installation est
cassée.


Avatar
FDA

Le problème actuel est que tous les logiciels ne supportent pas encore
UTF-8 (car multi-byte).


Un des gros problèmes d'UTF-8 n'est-il pas la taille variable en octet
des caractères, qui doit terriblement ralentir les opérations
d'extraction de sous-chaîne sur position ? J'imagine l'inefficacité
*intrinsèque* , par exemple, d'instructions du genre

substr $a, 6000, 2000

lorsqu'on passe de l'ASCII à l'UTF-8 :-(

Avec les caractères codés en taille variable, plus rien ne va de soi...
d'où sans doute les lenteurs de portage.

Avatar
Nicolas George
FDA wrote in message
<43a7300b$0$13113$:
Un des gros problèmes d'UTF-8 n'est-il pas la taille variable en octet
des caractères, qui doit terriblement ralentir les opérations
d'extraction de sous-chaîne sur position ?


L'accès direct à un caractère de chaîne par position est une opération
fondamentalement inutile. L'accès par position est pertinent pour un fichier
à format fixe, mais ça se fait avant encodage. Pour une chaîne, le mode
d'opération normal est de la parcourir à la recherche de motifs, typiquement
avec un moteur d'expression rationnelles ou un parseur plus évolué : dans
ces conditions, il est possible de noter un index direct vers les positions
retenues, qui n'est pas une position en caractères mais est exploitable
immédiatement pour toutes les opérations d'extraction de sous-chaîne.

Avatar
Vincent Lefevre
Dans l'article <do4bac$1cks$,
Nicolas George <nicolas$ écrit:

Vincent Lefevre wrote in message
<20051218183459$:
Si, justement ici j'ai cette contrainte: il faut sortir les messages
dans l'encodage utilisé par le terminal, i.e. celui indiqué par les
locales.


Il était question de l'encodage du script lui-même, pas de l'encodage des
messages qu'il affiche. Ces deux notions sont tout à fait indépendantes,
justement.


Bon alors je n'ai pas dû comprendre ton paragraphe. Pour le script,
je me restreins à l'ASCII, de manière à pouvoir le lire avec "less"
que je sois dans un terminal en ISO-8859-1 ou dans un terminal en
UTF-8. Je ne me préoccupe donc pas des problèmes d'encodage du script
(concernant l'interprétation par Perl).

Évidemment qu'il faut encoder STDERR. Mais les modules n'ont
probablement pas besoin de le faire, car ils sortent tout en
anglais.


Ce qui est une très bonne chose : la traduction des messages
d'erreur est une connerie qui cause des pertes de temps sans fin sur
les groupes de discussion d'entraide.


C'est discutable. Avec ce point de vue, il ne faut pas non plus
traduire l'interface. Dans la réalité, les programmes traduisent
stderr, à commencer par les coreutils, donc je dois supposer que
STDERR peut contenir des caractères non ASCII.

Il ne faut pas encoder STDERR, et il faut faire attention à la
garder en ASCII, de manière à ce que les informations données soient
lisibles au maximum en toutes circonstances, même quand la moitié de
l'installation est cassée.


STDERR, ça correspond aux messages d'erreur (destinés avant tout aux
utilisateurs), pas des messages de debug, ni des messages de log.

--
Vincent Lefèvre - Web: <http://www.vinc17.org/>
100% accessible validated (X)HTML - Blog: <http://www.vinc17.org/blog/>
Work: CR INRIA - computer arithmetic / SPACES project at LORIA


1 2 3