R: Чтение одного файла из каталога tar.gz - PullRequest
0 голосов
/ 04 января 2019

Рассмотрим файл tar.gz каталога, который содержит много отдельных файлов.

Изнутри R я могу легко извлечь имена отдельных файлов с помощью этой команды:

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

При использовании только R возможно ли непосредственно читать / загружать один из этих файлов в R (иначе без предварительной распаковки и записи файла на диск)?

1 Ответ

0 голосов
/ 05 января 2019

Возможно, но я не знаю ни одной чистой реализации (она может существовать). Ниже приведен базовый код R, который должен работать во многих случаях (например, имена файлов с полным путем внутри архива должны быть не более 100 символов). В некотором смысле, это просто повторная реализация «untar» чрезвычайно грубым способом, но таким образом, что он будет указывать на нужный файл в сжатом файле.

Первая проблема заключается в том, что вы должны читать только сжатый файл с самого начала. Использование «seek ()» для повторного позиционирования указателя файла на нужный файл, к сожалению, ошибочно в сжатом файле.

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 так, как если бы это был (соединение, указывающее на) ваш файл. Но он существует только в памяти.

...