alpine linux 上的 iconvlist() 不一致

Mar*_*cin 4 encoding r utf-8

我设置了一个基于artemklevtsov/r-alpine:latest. 当我运行R脚本时,我看到此错误:

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

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

它看起来像iconvlist()在 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,"
Run Code Online (Sandbox Code Playgroud)

所以UTF-8从来不匹配UTF-8,。以前有人遇到过这个问题吗?我在本地 Mac (OSX) 上获得的编码列表是正确的,并且没有尾随逗号。它也不会发生在 CentOS 上,所以它看起来像是 alpine 特有的。

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

小智 5

我有同样的问题,这次来自调用read::read_csv,它使用base::iconvlist并给出相同的错误消息Invalid encoding UTF-8: defaulting to UTF-8.。这是关于alpine:3.12使用 R 3.6.3 提供的apk add R,根据下面的详细信息,我认为该问题将出现在任何版本的 alpine 和 R 上,除非采取措施直接解决它。

我找到了几个解决方案。总而言之:

  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"
Run Code Online (Sandbox Code Playgroud)

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

sed -i 's/,//g' /usr/lib/R/library/utils/iconvlist
Run Code Online (Sandbox Code Playgroud)

随后的调用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
Run Code Online (Sandbox Code Playgroud)

为什么这是必要的?

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

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

R 的base::iconvlist()函数将首先尝试通过使用预编译的 C 代码获取编码.Internal(iconv(...,如果可用,它将调用iconvlist(在 C 中)。由于iconvlistalpine 上不存在 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)
Run Code Online (Sandbox Code Playgroud)

为什么iconvlist文件格式不正确?

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

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

解决方案2

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

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

apk add gnu-libiconv gnu-libiconv-dev
Run Code Online (Sandbox Code Playgroud)

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

export CFLAGS=-I/usr/include/gnu-libiconv $CFLAGS
Run Code Online (Sandbox Code Playgroud)

运行./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)

Run Code Online (Sandbox Code Playgroud)

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)
Run Code Online (Sandbox Code Playgroud)