我有同样的问题,这次是通过调用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:
- 从
system.file("iconvlist", package = "utils") 的文件中删除逗号,或者
- 使用 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)