iconvlist()在alpine linux

时间:2017-05-23 19:56:11

标签: r encoding utf-8

我有一个基于artemklevtsov/r-alpine:latest的docker容器设置。当我运行R脚本时,我看到了这个错误:

Invalid encoding UTF-8: defaulting to UTF-8.

我在httr库中跟踪了这段代码: https://github.com/hadley/httr/blob/master/R/content-parse.r#L5

在高山回归编码上看起来像iconvlist(),在末尾有一个尾随逗号,例如:

iconvlist()
 [1] "..."        "ISO8859-1," "ISO8859-2," "ISO8859-3," "ISO8859-4,"
 [6] "ISO8859-5," "ISO8859-6," "ISO8859-7," "UCS-2BE,"   "UCS-2LE,"
[11] "US_ASCII,"  "UTF-16BE,"  "UTF-16LE,"  "UTF-32BE,"  "UTF-8,"

因此UTF-8永远不会匹配UTF-8,。有没有人遇到过这个问题?我在本地Mac(OSX)上获得的编码列表是正确的,没有逗号。它也不会发生在CentOS上,所以它看起来像是高山特有的。

有没有办法解决这个问题?可能通过R中的配置或修改iconvlist()输出?

1 个答案:

答案 0 :(得分:2)

这次,我遇到了同样的问题,即调用read::read_csv,后者使用base::iconvlist并给出相同的错误消息Invalid encoding UTF-8: defaulting to UTF-8.。这是在alpine:3.12上使用apk add R提供的R 3.6.3,基于以下详细信息,我认为该问题将出现在任何版本的alpine和R上,除非已采取步骤直接解决该问题。

我找到了几种解决方案。 TLDR:

  1. system.file("iconvlist", package = "utils")的文件中删除逗号,或者
  2. 使用gnu-libiconv库重新编译R,以获得更全面的iconv支持。

解决方案1 ​​

base::iconvlist()函数使用iconvlist文件作为后备方法来获取系统支持的编码列表。在高山上,出于以下概述的原因,将始终使用此后备方法,但是iconvlist文件中包含逗号,R对此不期望。

最简单的解决方案是从iconvlist文件中删除逗号(可以在base::system.file()中找到)。

> system.file("iconvlist", package = "utils")
[1] "/usr/lib/R/library/utils/iconvlist"

从命令行(不是R)中删除逗号的一种方法是:

sed -i 's/,//g' /usr/lib/R/library/utils/iconvlist

随后对base::iconvlist()的调用将读取并解析新文件而没有逗号,而依赖于base::iconvlist()的其他函数将能够成功检查支持,例如表示“ UTF-8”。

> iconvlist()
 [1] "..."       "ISO8859-1" "ISO8859-2" "ISO8859-3" "ISO8859-4" "ISO8859-5"
 [7] "ISO8859-6" "ISO8859-7" "UCS-2BE"   "UCS-2LE"   "US_ASCII"  "UTF-16BE" 
[13] "UTF-16LE"  "UTF-32BE"  "UTF-8"     "UTF32-LE"  "WCHAR_T"

> "UTF-8" %in% iconvlist()
[1] TRUE

为什么这有必要?

字符编码的国际转换(iconvR Administration and Installation Manual中规定的R期望由操作系统提供的功能。操作系统提供自己的iconv功能实现,有时功能更少。由于阿尔卑斯山的设计极小,因此仅提供满足POSIX标准所需的东西就不足为奇了。

当R构建在系统上时,它首先检查主机C开发库对iconv的支持程度,然后再将功能编译到R的内部。至关重要的是,检查对 C函数 iconvlist的支持,这在高山上是不存在的,如R:checking for iconvlist... no的apk build log所示,因此该C函数在内部R不可用。

R的base::iconvlist()函数将首先尝试通过.Internal(iconv(...使用预编译的C代码获取编码,如果可用,它将调用iconvlist(在C中)。由于高山上不存在iconvlist C函数,因此此.Internal调用将始终返回NULL,而R函数将回退以从iconvlist文件中读取信息:

> iconvlist
function () 
{
    int <- .Internal(iconv(NULL, "", "", "", TRUE, FALSE))
    if (length(int)) 
        return(sort.int(int))
    icfile <- system.file("iconvlist", package = "utils")
# ... (truncated)

为什么iconvlist文件的格式意外?

当R为built时,通过列出可用编码的命令iconvlist创建iconv -l文件。这是/usr/bin/iconv中不是或C函数的实用程序。 iconv -l的输出格式没有标准。 Alpine尝试符合POSIX标准,而这些仅要求-l选项将“值以未指定的格式写入标准输出”。

R期望文件格式包含用空格分隔的值(base::iconvlist()strsplit(ext, "[[:space:]]")解析文件),这对于其他Linux变体(例如, Debian,CentOS,但不适用于带有逗号的alpine musl libc version

解决方案2

更严格的解决方案是使用提供iconv C函数的备用iconvlist C库实现从源代码构建R。 base::iconvlist()然后可以通过其.Internal(iconv(...调用来获取编码,而无需回退到iconvlist文件。

提供iconvlist的实现是GNU libiconv,该实现已打包为alpine并可以通过以下方式安装:

apk add gnu-libiconv gnu-libiconv-dev

软件包gnu-libiconv-dev/usr/include/gnu-libiconv/中提供了标头,因此这里需要优先指向编译器,而不是/usr/include中的现有编译器。这超出了我的专业知识,但是可以通过在-I/usr/include/gnu-libiconv环境变量中添加CFLAGS来完成。

export CFLAGS=-I/usr/include/gnu-libiconv $CFLAGS

运行./configure会产生类似于以下内容的检查结果:

... (truncated)
checking for iconv.h... yes
checking for iconv... in libiconv
checking whether iconv accepts "UTF-8", "latin1", "ASCII" and "UCS-*"... yes
checking whether iconv accepts "CP1252"... yes
checking for iconvlist... yes
... (truncated)

make之后,我可以运行./bin/R,即使iconvlist文件仍然包含逗号,对base::iconvlist()的调用也会产生格式正确的结果:

> iconvlist()
  [1] "850"                                          
  [2] "862"                                          
  [3] "866"                                          
  [4] "ANSI_X3.4-1968"                               
  [5] "ANSI_X3.4-1986"
... (truncated)

# The unsorted list is coming from the internal C functions, not the file
> .Internal(iconv(NULL, "", "", "", TRUE, FALSE))
  [1] "ANSI_X3.4-1968"                               
  [2] "ANSI_X3.4-1986"                               
  [3] "ASCII"                                        
  [4] "CP367"                                        
  [5] "IBM367"
... (truncated)