【问题标题】:R: Read single file from within a tar.gz directoryR:从 tar.gz 目录中读取单个文件
【发布时间】:2019-01-09 18:41:56
【问题描述】:

考虑一个包含大量单个文件的目录的 tar.gz 文件。

在 R 中,我可以使用以下命令轻松提取单个文件的名称:

fileList <- untar(my_tar_dir.tar.gz, list=T)

仅使用 R 是否可以直接将其中一个文件读取/加载到 R 中(也就是无需先解压缩文件并将其写入磁盘)?

【问题讨论】:

  • 你见过unzip a tar.gz file in R? 接受的答案似乎解决了仅提取一个文件的问题。
  • 啊,是的,我可以看出我不够具体——我不想打开任何东西,而是直接读进去。相应地更新了问题。

标签: r tar tidyverse


【解决方案1】:

这是可能的,但我不知道任何干净的实现(它可能存在)。下面是一些非常基本的 R 代码,在许多情况下都可以使用(例如,存档中包含完整路径的文件名应少于 100 个字符)。在某种程度上,它只是以一种极其粗糙的方式重新实现“解压缩”,但它会指向一个 gzip 压缩文件中的所需文件。

第一个问题是您应该只从一开始就读取 gzip 压缩文件。不幸的是,使用“seek()”将文件指针重新定位到所需文件在 gzip 压缩文件中是不稳定的。

ParseTGZ<- function(archname){
  # open tgz archive
  tf <- gzfile(archname, open='rb')
  on.exit(close(tf))
  fnames <- list()
  offset <- 0
  nfile <- 0
  while (TRUE) {
    # go to beginning of entry
    # never use "seek" to re-locate in a gzipped file!
    if (seek(tf) != offset) readBin(tf, what="raw", n= offset - seek(tf))
    # read file name
    fName <- rawToChar(readBin(tf, what="raw", n=100))
    if (nchar(fName)==0) break
    nfile <- nfile + 1
    fnames <- c(fnames, fName)
    attr(fnames[[nfile]], "offset") <- offset+512
    # read size, first skip 24 bytes (file permissions etc)
    # again, we only use readBin, not seek()
    readBin(tf, what="raw", n=24)
    # file size is encoded as a length 12 octal string, 
    # with the last character being '\0' (so 11 actual characters)
    sz <- readChar(tf, nchars=11) 
    # convert string to number of bytes
    sz <- sum(as.numeric(strsplit(sz,'')[[1]])*8^(10:0))
    attr(fnames[[nfile]], "size") <- sz
#    cat(sprintf('entry %s, %i bytes\n', fName, sz))
    # go to the next message
    # don't forget entry header (=512) 
    offset <- offset + 512*(ceiling(sz/512) + 1)
  }
# return a named list of characters strings with attributes?
  names(fnames) <- fnames
  return(fnames)
}

这将为您提供 tar.gz 存档中所有文件的确切位置和长度。 现在下一步是实际提取单个文件。您可以通过直接使用“gzfile”连接来做到这一点,但在这里我将使用 rawConnection()。这假定您的文件适合内存。

extractTGZ <- function(archfile, filename) {
  # this function returns a raw vector
  # containing the desired file
  fp <- ParseTGZ(archfile)
  offset <- attributes(fp[[filename]])$offset
  fsize <- attributes(fp[[filename]])$size
  gzf <- gzfile(archfile, open="rb")
  on.exit(close(gzf))
  # jump to the byte position, don't use seek()
  # may be a bad idea on really large archives...
  readBin(gzf, what="raw", n=offset)
  # now read the data into a raw vector
  result <- readBin(gzf, what="raw", n=fsize)
  result
}

现在,终于:

ff <- rawConnection(ExtractTGZ("myarchive", "myfile"))

现在您可以将ff 视为(指向)您的文件的连接。但它只存在于内存中。

【讨论】:

  • 非常好的解决方案 - 谢谢!在 extractTGZ 函数中是否需要额外的 on.exit(close(gzf)) ?
  • 确实!我在答案中添加了这个。
  • 关于评论“#对于非常大的档案可能是个坏主意...”如果您在 tar.gz 档案中有数千个(小)文件,您会采取不同的做法吗?
  • 不是文件数量,而是总大小。如果您的(解压缩的)存档非常大(例如 >1GB),那么 readBin() 将暂时创建一个非常大的向量。在这种情况下,读取 10 x 100MB 可能会更好。我知道,读取所有这些数据并不理想,但这是避免 seek() 的最简单方法。对于成千上万的小文件,这将是第一步(ParseTGZ),可能会变得相当慢。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-03-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多