【问题标题】:iconvlist() inconsistency on alpine linuxalpine linux上的iconvlist()不一致
【发布时间】:2017-10-23 22:40:56
【问题描述】:

我有一个基于 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() on alpine 返回末尾有逗号的编码,例如:

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 上发生,所以看起来它是 alpine 特有的。

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

【问题讨论】:

    标签: r encoding utf-8


    【解决方案1】:

    我有同样的问题,这次是通过调用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 文件作为备用方法来获取系统支持的编码列表。在 alpine 上,将始终使用这种后备方法,原因如下所述,但 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
    

    为什么需要这样做?

    字符编码的国际转换 (iconv) 是 R 期望操作系统提供的功能,在 R Administration and Installation Manual 中规定。操作系统提供自己的iconv 功能实现,有时功能较少。由于 alpine 被设计为最小化,因此它仅提供满足 POSIX 标准所需的内容也就不足为奇了。

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

    R 的base::iconvlist() 函数将首先尝试通过.Internal(iconv(... 使用预编译的C 代码获取编码,如果可用,它将调用iconvlist(在C 中)。由于 alpine 上不存在 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 文件的格式异常?

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

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

    解决方案 2

    更严格的解决方案是使用提供iconvlist C 函数的替代iconv 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)
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-11-03
      • 2019-06-14
      • 1970-01-01
      • 2019-06-09
      • 2016-09-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多