[Erlang 00124] Erlang Unicode 两三事 - 补遗

最近看了Erlang User Conference 2013上patrik分享的BRING UNICODE TO ERLANG!视频,这个分享很好的梳理了Erlang Unicode相关的问题,基本上把 Using Unicode in Erlang 讲解了一遍.再次学习了一下,整理成文字,补充一些 [Erlang 0062] Erlang Unicode 两三事 遗漏掉的内容.

视频在这里: http://www.youtube.com/watch?v=M6hPLCA0F-Y

PDF在这里: http://www.erlang-factory.com/upload/presentations/847/PatrikEUC2013.pdf

演变

Erlang对Unicode的支持受到环境变量和OTP版本的影响,多数时候影响的是Unicode data如何显示,下面是各个版本的OTP对Unicode支持的演变情况简介:

问题域

我们梳理一下,Erlang Unicode的问题域包含哪些具体的细节:

Erlang Shell
   Erlang Shell中是否可以输入中文
   Erlang Shell中是否可以显示中文
Erlang Code
   代码文件的编码方式
Files
   文件名如何解析

下面我们逐一击破:

Erlang Shell中是否可以输入中文

LANG and LC_CTYPE环境变量影响Erlang Shell,告诉终端程序是否要处理unicode,(此环境变量还会影响文件名的解析,OTP17.0默认会使用+fna,稍后会提到),检查这个参数可以使用io:getopts().可以输出一下 echo $LANG 和 echo $LC_CTYPE看一下,比如在我的centos机器上,环境变量是

# echo $LANG
en_US.UTF-8

下面我们设置变量为latin

 LC_CTYPE=en_US.ISO-8859-1  /usr/local/bin/erl

  

尝试输入一下中文,看看情况以后多怪异.遇到这种情况怎么破?io:setopts([{encoding,unicode}]). 即可,下面的测试一开始使用latin参数启动,尝试输入中文"我们"虽然显示的List是正确的[230,136,145,228,187,172].但是字符显示是错的.然后通过setopts设置encoding为unicode解决此问题.

 LC_CTYPE=en_US.ISO-8859-1  /usr/local/bin/erl
Erlang R16B02 (erts-5.10.3) [source] [64-bit] [smp:2:2] [async-threads:10] [hipe] [kernel-poll:false]

Eshell V5.10.3  (abort with ^G)
1> io:getopts().
[{expand_fun,#Fun<group.0.56199974>},
{echo,true},
{binary,false},
{encoding,latin1}]
2> "210\221[C.
[230,136,145,228,187,172]
3>  io:setopts([{encoding,unicode}]).
ok
4>
4> "我们".
[25105,20204]
5>

  

Erlang启动使用了-oldshell 或者 -noshell的时候会默认使用latin1(bytewise encoding 即单字节表达一个字符),交互式Shell启动的时候会按照环境变量配置选择编码方式.在Erlang启动完成之后可以使用io:setopts来修改全局的编码方式,不管系统启动伊始使用的是什么参数.看下面的测试:

Erlang/OTP 17 [erts-6.0] [source] [64-bit] [smp:4:4] [async-threads:10] [hipe] [kernel-poll:false]
Eshell V6.0  (abort with ^G)
1> "我们".
"我们"
2> io:format("~tp",[v(1)]).
"我们"ok
4> io:format("~ts",[v(1)]).
我们ok
5> io:format("~ts",[lists:seq(20204,20220)]).
们仭仮仯仰仱仲仳仴仵件价仸仹仺任仼ok
6> io:format("~ts",[lists:seq(20204,20290)]).
们仭仮仯仰仱仲仳仴仵件价仸仹仺任仼份仾仿伀企伂伃伄伅伆伇伈伉伊伋伌伍伎伏伐休伒伓伔伕伖众优伙会伛伜伝伞伟传伡伢伣伤伥伦伧伨伩伪伫伬伭伮伯估伱伲伳伴伵伶伷伸伹伺伻似伽伾伿佀佁佂ok
7> io:getopts().
[{expand_fun,#Fun<group.0.100149429>},
{echo,true},
{binary,false},
{encoding,unicode}]
8> io:setopts([{encoding,latin1}]).
ok
9> io:format("~ts",[lists:seq(20204,20290)]).
\x{4EEC}\x{4EED}\x{4EEE}\x{4EEF}\x{4EF0}\x{4EF1}\x{4EF2}\x{4EF3}\x{4EF4}\x{4EF5}\x{4EF6}\x{4EF7}\x{4EF8}\x{4EF9}\x{4EFA}\x{4EFB}\x{4EFC}\x{4EFD}\x{4EFE}\x{4EFF}\x{4F00}\x{4F01}\x{4F02}\x{4F03}\x{4F04}\x{4F05}\x{4F06}\x{4F07}\x{4F08}\x{4F09}\x{4F0A}\x{4F0B}\x{4F0C}\x{4F0D}\x{4F0E}\x{4F0F}\x{4F10}\x{4F11}\x{4F12}\x{4F13}\x{4F14}\x{4F15}\x{4F16}\x{4F17}\x{4F18}\x{4F19}\x{4F1A}\x{4F1B}\x{4F1C}\x{4F1D}\x{4F1E}\x{4F1F}\x{4F20}\x{4F21}\x{4F22}\x{4F23}\x{4F24}\x{4F25}\x{4F26}\x{4F27}\x{4F28}\x{4F29}\x{4F2A}\x{4F2B}\x{4F2C}\x{4F2D}\x{4F2E}\x{4F2F}\x{4F30}\x{4F31}\x{4F32}\x{4F33}\x{4F34}\x{4F35}\x{4F36}\x{4F37}\x{4F38}\x{4F39}\x{4F3A}\x{4F3B}\x{4F3C}\x{4F3D}\x{4F3E}\x{4F3F}\x{4F40}\x{4F41}\x{4F42}ok
10> io:format("~ts",[v(1)]).
\x{6211}\x{4EEC}ok
11> io:setopts([{encoding,unicode}]).
ok
12> io:format("~ts",[v(1)]).
我们ok
13> io:format("~ts",[lists:seq(20204,20290)]).
们仭仮仯仰仱仲仳仴仵件价仸仹仺任仼份仾仿伀企伂伃伄伅伆伇伈伉伊伋伌伍伎伏伐休伒伓伔伕伖众优伙会伛伜伝伞伟传伡伢伣伤伥伦伧伨伩伪伫伬伭伮伯估伱伲伳伴伵伶伷伸伹伺伻似伽伾伿佀佁佂ok
14>

Erlang Shell中是否可以显示中文

之前提到过的Erlang Shell 中显示文本常量的各种奇怪,其实源于字符串启发式检测机制("Heuristic String Detection"),简单讲就是Erlang Shell会检测List,Binary里面的数据是否可以有可打印的字符,比如下面的二进制串<<230,136,145,228,187,172,229,173,166,228,185,160>>.就被认为检测到可打印的,就输出了<<"我们学习"/utf8>>.

还记得那个输出数据的技巧吗?输出的数据内容被Shell自作聪明的打印成了字符,怎么解决的呢?在数据尾部追加一个0,比如[25105].会打印出来"我",[25105,0]就原样输出了.这个技巧实际上是通过加0避开了"Heuristic String Detection"机制.做下面的实验需要注意的一点是启动erl的参数:

erl +pc unicode

  

+pc 这个选项的作用就是选择Shell可打印字符的范围,可以是erl +pc latin1 或者  erl +pc unicode,在紧接着的实验里面,[25105]被如实显示并没有被解析显示成"我".

默认情况下,erl启动参数是latin

# erl +pc unicode
Erlang/OTP 17 [erts-6.0] [source] [64-bit] [smp:2:2] [async-threads:10] [hipe] [kernel-poll:false]
Eshell V6.0  (abort with ^G)
1> <<230,136,145,228,187,172,229,173,166,228,185,160>>.
<<"我们学习"/utf8>>
2> <<230,136,145,228,187,172,229,173,166,228,185,160,69,114,108,97,110,103>>.
<<"我们学习Erlang"/utf8>>
3> $我.
25105
4> <<230,136,145,228,187,172,229,173,166,228,185,160,69,114,108,97,110,103,0>>.
<<230,136,145,228,187,172,229,173,166,228,185,160,69,114,
  108,97,110,103,0>>
5> [25105].
"我"
6> [25105,0].
[25105,0]

 

# erl
Erlang/OTP 17 [erts-6.0] [source] [64-bit] [smp:2:2] [async-threads:10] [hipe] [kernel-poll:false]
Eshell V6.0  (abort with ^G)
1> $我.
25105
2> [25105].
[25105]
3>

io:printable_range/0 和  io_lib:printable_list/1这两个函数可以帮助我们检查当前shell的可打印字符的范围,判断一个List是否属于可打印的.看下面的例子:

erl +pc unicode
Erlang/OTP 17 [erts-6.0] [source] [64-bit] [smp:2:2] [async-threads:10] [hipe] [kernel-poll:false]
Eshell V6.0  (abort with ^G)
1> io_lib:printable_list([25105,20204]).
true
2> [25105,20204].
"我们"
3>  io:printable_range().
unicode
4>

  

 erl
Erlang/OTP 17 [erts-6.0] [source] [64-bit] [smp:2:2] [async-threads:10] [hipe] [kernel-poll:false]
Eshell V6.0  (abort with ^G)
1> io_lib:printable_list([25105,20204]).
false
2> [25105,20204].
[25105,20204]
3>  io:printable_range().
latin1
4>

 

这个启发机制(heuristics)同样被io(_lib):format/2使用,~tp会受到+pc参数的影响,~ts不会.

# erl +pc unicode

7> io:format("~ts",[[25105]]).
我ok
8> io:format("~tp",[[25105]]).
"我"ok
9>

# erl +pc latin1 

3> io:format("~ts",[[25105]]).

我ok

4> io:format("~tp",[[25105]]).

[25105]ok

5> 

代码文件的编码方式

Erlang源代码进行编译的时候,如果文件添加了注释头自然好办,如果没有就会按照默认的编码方式解析文件,epp:default_encoding/0返回的就是当前OTP版本使用的默认编码方式.R16B是 latin1, 17.0是utf8.

-module(coding).
-compile(export_all).
a()->
  "我们学习Erlang".

  

Erlang/OTP 17 [erts-6.0] [source] [64-bit] [smp:2:2] [async-threads:10] [hipe] [kernel-poll:false]
Eshell V6.0  (abort with ^G)
1> coding:a().
[25105,20204,23398,20064,69,114,108,97,110,103]
2> io:format("~ts",[v(1)]).
我们学习Erlangok
3> q().
ok
4>

  

在R16B代码文件的默认编码还是latin,所以下面的代码在R16B中输出是这样的:

Erlang R16B02 (erts-5.10.3) [source] [64-bit] [smp:2:2] [async-threads:10] [hipe] [kernel-poll:false]

Eshell V5.10.3  (abort with ^G)
1> coding:a().
[230,136,145,228,187,172,229,173,166,228,185,160,69,114,108,
97,110,103]
2> io:format("~ts",[v(1)]).
æ??ä»¬å­¦ä¹ Erlangok
3>

  

还是在R16所在的机器上,我们修改一下代码,添加声明文件编码的注释头

%% -*- coding: utf-8 -*-
-module(coding).
-compile(export_all).
a()->
  "我们学习Erlang".

  

如果要显示指定文件是latin编码,可以添加注释头 %% -*- coding: latin-1 -*- ,参考这里[链接].

下面我们在之前的测试代码文件中添加一个方法,返回一个二进制序列:

b()->
  <<"我们学习Erlang">>.

  

#  /usr/local/bin/erl
Erlang R16B02 (erts-5.10.3) [source] [64-bit] [smp:2:2] [async-threads:10] [hipe] [kernel-poll:false]
Eshell V5.10.3  (abort with ^G)
1> coding:b().
<<17,236,102,96,69,114,108,97,110,103>>
2> io:format("~ts",[v(1)]).
^Qìf`Erlangok
3> q().
ok

  

你可能会猜测是+pc unicode的原因吗?好吧,明知不是我们还是试一下:

#  /usr/local/bin/erl +pc unicode
Erlang R16B02 (erts-5.10.3) [source] [64-bit] [smp:2:2] [async-threads:10] [hipe] [kernel-poll:false]

Eshell V5.10.3  (abort with ^G)
1> coding:b().
<<17,236,102,96,69,114,108,97,110,103>>
2>  io:format("~ts",[v(1)]).
^Qìf`Erlangok
3> q().
ok

  

问题在什么地方?对utf8描述符

b()->
  <<"我们学习Erlang"/utf8>>.
  

#  /usr/local/bin/erl
Erlang R16B02 (erts-5.10.3) [source] [64-bit] [smp:2:2] [async-threads:10] [hipe] [kernel-poll:false]
Eshell V5.10.3  (abort with ^G)
1> coding:b().
<<230,136,145,228,187,172,229,173,166,228,185,160,69,114,
  108,97,110,103>>
2> io:format("~ts",[v(1)]).
我们学习Erlangok
3>

  

做下简单的实验看看这两者的区别:

 erl
Erlang/OTP 17 [erts-6.0] [source] [64-bit] [smp:2:2] [async-threads:10] [hipe] [kernel-poll:false]

Eshell V6.0  (abort with ^G)
1>  "αβ" .
[945,946]
2>
2>  <<"αβ">> .
<<"±²">>
3>
3>  <<"αβ"/utf8>> .
<<206,177,206,178>>
5> <<177,178>>.
<<"±²">>

  

上面的第2行测试代码是怎么回事呢?输出的是个什么东西呢?看一下第5行代码就可以了,输出的是<<177,178>>,换句话说数据显示的时候被截断了.

文件名如何解析

至于文件名是否包含unicode,除非是文件名是不可控的外部资源,否则这个问题是可以通过项目规约规避掉的,没有必要通过代码/技术手段解决这个问题.

erl启动的时候添加不同的flag可以控制解析文件名的方式: +fnl 按照latin去解析文件名 +fnu 按照unicode解析文件名 +fna 是根据环境变量自动选择,这也是目前的系统默认值.可以使用file:native_name_encoding检查此参数.

Eshell V5.10.3  (abort with ^G)
1> file:native_name_encoding().
latin1
2> 

Eshell V6.0  (abort with ^G)
1> file:native_name_encoding().
utf8
2>

 

最后 

unicode,io,file,group,user,re,wx,string这些模块在遇到unicode的时候要特别注意下.应该有不少人在正则这里栽跟头了吧, Using Unicode in Erlang 文档信息量很大,最后还有一些常见问题的解决以及代码,有兴趣的可以动手实践一下,今天就到这里.

[Erlang 00124] Erlang Unicode 两三事 - 补遗

时间: 2024-11-06 16:39:29

[Erlang 00124] Erlang Unicode 两三事 - 补遗的相关文章

[Erlang 0129] Erlang 杂记 VI

把之前阅读资料的时候记下的东西,整理了一下. Adding special-purpose processor support to the Erlang VM   P23 简单介绍了Erlang Compiler和Beam文件格式; The Erlang Compiler in short 章节提到了 Core Erlang 这个之前有提到过: [Erlang 0120] Know a little Core Erlang http://www.cnblogs.com/me-sa/p/know

[Erlang危机]Erlang In Danger 序言

原创文章,转载请注明出处:服务器非业余研究http://blog.csdn.net/erlib 作者Sunface?? Introduction On Running Software 运行时软件 There's something rather unique in Erlang in how it approaches failure compared to most other programming languages. There's this common way of thinkin

[Erlang 0122] Erlang Resources 2014年1月~6月资讯合集

虽然忙,有些事还是要抽时间做; Erlang Resources 小站 2014年1月~6月资讯合集,方便检索. 小站地址: http://site.douban.com/204209/ 1月   114 RR Elixir with José Valim by CHARLES MAX WOOD on JULY 17, 2013 http://rubyrogues.com/114-rr-elixir-with-jose-valim/ "The Erlang Runtime System"

[Erlang 0057] Erlang 排错利器: Erlang Crash Dump Viewer

http://www.cnblogs.com/me-sa/archive/2012/04/28/2475556.html Erlang Crash Dump Viewer真的是排错的天兵神器,还记得我们之前曾经讨论过[Erlang 0013]抓取Erlang进程运行时信息 [Erlang 0012]Erlang Process input queue ,下面是我梳理的"How to interpret the Erlang crash dumps"的文档; 很多人在问有什么工具可以打开

Erlang入门(五)——补遗

暂时搞不到<Programming Erlang>,最近就一直在看Erlang自带的例子和Reference Manual.基础语法方面有一些过去遗漏或者没有注意的,断断续续仅记于此. 1.Erlang的保留字有: after and andalso band begin bnot bor bsl bsr bxor case catch cond div end fun if let not of or orelse query receive rem try when xor 基本都是些用于

Erlang 101 Erlang环境和顺序编程 - should done in 2014-10-26

笔记系列 Erlang环境和顺序编程 Erlang并发编程 Erlang分布式编程 Yaws Erlang/OTP 前言 拖了很久很久了,终于快忘的差不多了. Agenda 0 范围 环境 Erlang运行时环境.Emacs开发环境 顺序编程 数据结构 语法 1 Erlang环境搭建 Erlang website http://www.erlang.org/ 实践版本 otp_win32_R16B/erl5.10.1 安装路径记为ERLANG_HOME=D:/erl5.10.1 获取环境变量的方

Erlang 102 Erlang并发编程 - should be done in 2014-11-09

笔记系列 Erlang环境和顺序编程Erlang并发编程Erlang分布式编程YawsErlang/OTP 日期              变更说明2014-11-02 A outline,1 Agenda 0 范围 Erlang的现实世界建模方式 Erlang进程创建 Erlang进程设计模式 Erlang进程错误处理 1 Erlang Concurrency Modeling Philosophy Armstrong在[2]中跳出程序语言在处理并发应用场景时洋洋洒洒的急迫性.跃跃欲试的一站式

Erlang 103 Erlang分布式编程.- 缺2~4

Outline 笔记系列 Erlang环境和顺序编程Erlang并发编程Erlang分布式编程YawsErlang/OTP 日期              变更说明 2014-11-23 A Outline   A 1.1-1.22014-12-08 A 1.3 Agenda 0范围 节点和通信 基本分布式编程模块 empd进程 套接字编程 1 Erlang节点和通信 1.1节点 一个Erlang节点是已命名的(named)的正在运行的Erlang运行时系统(erts). 多个节点可以运行在一台

Erlang 在erlang项目中使用protobuf

protobuf是google的一个序列化框架,类似XML,JSON,其特点是基于二进制,比XML表示同样一段内容要短小得多,还可以定义一些可选字段,广泛用于服务端与客户端通信.文章将着重介绍在erlang中如何使用protobuf. 首先google没有提供对erlang语言的直接支持,所以这里使用到的第三方的protobuf库( erlang_protobuffs ) 定义一个protobuf结构,保存为test.proto,如下: message Person { required int