iconv是linux下的编码转换的工具,它提供命令行和函数接口两种支持方式。
一. 命令行
Linux下的iconv命令可以实现文件编码方式的转换,可以输入iconv --help命令查看。
iconv用法如下: iconv [选项…] [文件…]
选项有以下几种,可以结合使用。下面是通过help命令查看的部分使用信息:
输入/输出格式规范:
-f, --from-code=名称 原始文本编码
-t, --to-code=名称 输出编码
信息:
-l, --list 列举所有已知的字符集(不区分大小写)
输出控制:
-c 从输出中忽略无效的字符
-o, --output=FILE 输出文件
-s, --silent 关闭警告
–verbose 打印进度信息
-?, --help 给出该系统求助列表
–usage 给出简要的用法信息
-V, --version 打印程序版本号
其中,-f、-t、-l等是缩写,逗号后面的“–from-code=名称”、“–list”等是完整的写法。使用 –f 时,后面要紧跟所要转换的文件当前的编码格式,文件编码格式可以通过enca或file命令查看,也可以使用Vi/Vim打开文件后输入”: set fileencoding”进行查看;-t 后面紧跟的是想要将文件转换成的编码格式,可以通过选项中的 –l命令查看iconv目前支持的所有编码方式;-o用来指定输出到哪一个文件,如果要对某个文件进行编码转换,必须指定另一个文件作为输出文件(可以使用 –o 也可以使用 >),否则iconv并不会对该文件进行转换。
确切的说,iconv命令的功能是将目标文件转换编码方式,并将转换后的文件内容输出到输出文件中,而不是直接修改目标文件。另外,尽管使用iconv命令同时转换多个文件的编码格式时,不会有任何错误提示,但是客观上还是认为它只能转换单个文件。
下面通过一些例子来详细说明:
① iconv –version 查看iconv程序的版本号以及一些相关信息
② iconv –l 查看iconv当前能支持转换的所有编码格式
③ iconv –f ASCII –t UNICODE file.c 不会转换,也不报错。file.c的编码格式依然时ASCII
④ iconv –f ASCII –t UNICODE file.c –o out.c 将file.c转换为UNICODE编码,生成文件out.c
⑤ iconv –f ASCII –t UNICODE file.c > out.c 同④
⑥ iconv –f ASCII –t UNICODE file.c file1.c –o out.c 将ASCII编码格式的file.c和file1.c转换成UNICODE编码,并输出到文件out.c中。这里会将file.c和file1.c的内容分别转换,并合并到文件out.c中。
⑦ iconv –f ASCII –t UNICODE file.c file1.c –o out.c out1.c 提示“无法打开输入文件“out1.c”: 没有那个文件或目录”。这里会将file.c和file1.c的内容转换,合并到文件out.c中;而不是将file.c转换,输出到out.c,再将file1.c转换,输出到out1.c。可见,在实际使用时,如果要同时对多个文件进行编码格式转换的话,iconv并不能按照我们的期望,进行一对一的转换输出,因此不推荐使用。
二. 函数接口
函数接口的方式可以实现将文件中的某些字符转换为另一种编码格式。比如,我之前通过excel库libxl.a中的函数读取某个工作表的名称,得到的表名的编码格式是UTF-8,而开发要求统一使用GBK,因此只能对这些名称进行编码转换来适应当前的程序。
GNU libiconv库中提供了编码转换的函数iconv(),使用时需要包含头文件<iconv.h>,编译时需要添加 –liconv 对库进行链接。同时libiconv库是开源的、跨平台的,使用该方法的程序可以很方便的移植到其它平台。详情请见:http://www.gnu.org/software/libiconv/
打开iconv.h头文件,我们可以看到libiconv库只有三个函数,这三个函数原型如下:
① iconv_open()
/* Allocate descriptor for code conversion from codeset FROMCODE to
codeset TOCODE. 给从代码集FROMCODE到代码集TOCODE的代码转换分配描述符
This function is a possible cancellation point and therefore not
marked with __THROW.*/这个函数是一个可能的消去点,因此没有使用__THROW标记
extern iconv_t iconv_open (const char *__tocode, const char *__fromcode);
THROW详情请见https://docs.microsoft.com/zh-cn/cpp/cpp/exception-specifications-throw-cpp?view=msvc-160&viewFallbackFrom=vs-2017 iconv_open()函数申请一个转换描述符,说明将要进行哪两种编码间的转换,tocode是目标编码,fromcode是源编码。转换描述符包含转换状态,调用icvon_open()后,转换处于初始状态;调用icvon()将会改变描述符的转换状态(这意味着转换描述符不能在多线程中使用)。
iconv_open()函数返回一个新申请的转换描述符,出错时返回( iconv_t) -1。iconv_t在iconv.h中的声明如下:
/* Identifier for conversion method from one codeset to another. */
typedef void *iconv_t; 从一个代码集到另一个代码集的转换方法的标识符
可见,icovn_open()函数的返回值只是用来表示转换的状态。
② iconv()
/* Convert at most *INBYTESLEFT bytes from *INBUF according to the
code conversion algorithm specified by CD and place up to
*OUTBYTESLEFT bytes in buffer at *OUTBUF. */
根据CD指定的代码转换算法,从INBUF指向的(内存中)转换最多INBYTESLEFT个字节的代码,然后将(这些编码转换后的代码中的)OUTBYTESLEFT 个字节放到OUTBUF指向
的缓冲区中。
extern size_t iconv (iconv_t __cd,
char **__restrict __inbuf,
size_t *__restrict __inbytesleft,
char **__restrict __outbuf,
size_t *__restrict __outbytesleft);
参数cd是函数iconv_open()返回的结果,表示哪两种编码之间进行转换;
参数inbuf指向我们用来存储待转换的字符串的内存空间;
参数inbytesleft指出需要将inbuf中的多少个字节的内容进行编码转换;
参数outbuf指向我们用来存储转换后的内容的缓冲区;如果需要使用转换后的字符串,切记不要使用outbuf指针来访问。
参数outbytesleft指出将转换后的inbuf中的内容存储多少个字节到outbuf中。
网上有一种说法,“inbytesleft用以记录还未转换的字符数,outbytesleft用以记录输出缓冲的剩余空间”应该是错误的。后面我们会加以验证。
③ iconv_close()
/* Free resources allocated for descriptor CD for code conversion.
将分配给代码转换描述符CD的资源释放
This function is a possible cancellation point and therefore not
marked with __THROW. */
extern int iconv_close (iconv_t __cd);
示例:
文件名:iconv.cpp
#include<iostream>
#include<string.h>
#include<iconv.h>
using namespace std;
int main()
{
char* src = "中国520" ; //文件保存为UTF-8格式,strlen(src) =9
char dest[256];
char* pbuff;
pbuff = &dest[0];
size_t inlen = strlen(src); //inlen = 9
size_t outlen = strlen(src)-1; //outlen = 8
//将src指向的内存中的数据前inlen个字节转换为GBK编码,然后将转换后的数据前outlen个字节存储到pbuff指向的缓冲区
iconv_t cd = iconv_open("GBK","UTF-8");
iconv(cd ,&src, &inlen, &pbuff, &outlen);
iconv_close(cd);
cout<<dest<<endl;
cout<<strlen(dest)<<endl;
}
编译命令如下: g++ iconv.cpp -o iconvo -std=c++11
运行结果如下:
这里因为转换9个字节,然后将前8个字节存储到pbuff中(实际转换后的字符串并没有8个字节,如上图所示只有7个字节),并不能直观的表达出inlen和outlen的真正意思。下面将outlen写为2,来观察结果如何。
#include<iostream>
#include<string.h>
#include<iconv.h>
using namespace std;
int main()
{
char* src = "中国520" ; //strlen(src) =9
char dest[256];
char* pbuff;
pbuff = &dest[0];
size_t inlen = strlen(src);
size_t outlen = 2; //这里可以写成3再进行比较
iconv_t cd = iconv_open("GBK","UTF-8");
iconv(cd ,&src, &inlen, &pbuff, &outlen);
iconv_close(cd);
cout<<dest<<endl;
cout<<strlen(dest)<<endl;
}
我们将”中国520”从UTF-8转换成GBK后,字符串所占的字节数从9Byte变成了7Byte。这是因为GBK规定中文字符和中文标点占2字节,英文字符和英文标点占1字节;而UTF-8中中文字符和标点占3字节,英文字符和标点仍占1字节。
从上面的程序可以看出,outlen表示将转换后的内容存储2个字节到pbuff指向的缓冲区,因此dest中只存储了一个中文字符,即“中”字。
可以将outlen改成3,观察结果。(和outlen = 2时结果一样)