Anexar arquivos enormes um ao outro sem copiá-los

41

Existem 5 arquivos enormes (arquivo1, arquivo2, .. arquivo5) com cerca de 10G cada e um espaço livre extremamente baixo disponível no disco e preciso concatenar todos esses arquivos em um. Não há necessidade de manter os arquivos originais, apenas o final.

A concatenação usual está catem sequência nos arquivos file2.. file5:

cat file2 >> file1 ; rm file2

Infelizmente, esse caminho requer um espaço livre de pelo menos 10G que não tenho. Existe uma maneira de concatenar arquivos sem copiá-lo de verdade, mas diga ao sistema de arquivos que o arquivo1 não termina no final do arquivo1 original e continua no início do arquivo2?

ps. sistema de arquivos é ext4, se isso importa.

pressa
fonte
2
Eu estaria interessado em ver uma solução, mas suspeito que não seja possível sem mexer diretamente com o sistema de arquivos.
Kevin
11
Por que você precisa ter um único arquivo físico que é tão grande? Estou perguntando, porque talvez você possa evitar concatenar - o que, como mostram as respostas atuais, é bastante incômodo.
Liori 23/06
6
@rush: então esta resposta pode ajudar: serverfault.com/a/487692/16081
liori
11
Uma alternativa ao mapeador de dispositivos, menos eficiente, mas mais fácil de implementar e resulta em um dispositivo particionável e pode ser usado em uma máquina remota é usar o modo "multi" de nbd-server.
Stéphane Chazelas
11
Eles sempre me chamam de idiota quando digo que acho que isso deve ser legal.
N611x007

Respostas:

19

AFAIK (infelizmente) não é possível truncar um arquivo desde o início (isso pode ser verdade para as ferramentas padrão, mas para o nível de syscall, veja aqui ). Mas, com a adição de alguma complexidade, você pode usar o truncamento normal (junto com arquivos esparsos): Você pode gravar no final do arquivo de destino sem ter gravado todos os dados no meio.

Vamos supor primeiro que os dois arquivos sejam exatamente 5GiB (5120 MiB) e que você deseja mover 100 MiB por vez. Você executa um loop que consiste em

  1. copiando um bloco do final do arquivo de origem para o final do arquivo de destino (aumentando o espaço em disco consumido)
  2. truncar o arquivo de origem em um bloco (liberando espaço em disco)

    for((i=5119;i>=0;i--)); do
      dd if=sourcefile of=targetfile bs=1M skip="$i" seek="$i" count=1
      dd if=/dev/zero of=sourcefile bs=1M count=0 seek="$i"
    done
    

Mas tente primeiro com arquivos de teste menores primeiro, por favor ...

Provavelmente, os arquivos não têm o mesmo tamanho nem múltiplos do tamanho do bloco. Nesse caso, o cálculo das compensações se torna mais complicado. seek_bytese skip_bytesdeve ser usado então.

Se esse é o caminho que você deseja seguir, mas precisa de ajuda para obter detalhes, pergunte novamente.

Atenção

Dependendo do ddtamanho do bloco, o arquivo resultante será um pesadelo de fragmentação.

Hauke ​​Laging
fonte
Parece que esta é a maneira mais aceitável de concatenar arquivos. Obrigado pelo conselho.
apressar
3
se não houver suporte a arquivos esparsos, então você pode bloquear-wise inverter o segundo arquivo no lugar e, em seguida, basta remover o último bloco e adicioná-lo para o segundo arquivo
aberração catraca
11
Eu não tentei isso sozinho (embora eu esteja prestes a), mas seann.herdejurgen.com/resume/samag.com/html/v09/i08/a9_l1.htm é um script Perl que afirma implementar esse algoritmo.
Zwol
16

Em vez de reunir os arquivos em um arquivo, talvez simule um único arquivo com um pipe nomeado, se o seu programa não puder lidar com vários arquivos.

mkfifo /tmp/file
cat file* >/tmp/file &
blahblah /tmp/file
rm /tmp/file

Como Hauke ​​sugere, losetup / dmsetup também pode funcionar. Um experimento rápido; Criei 'file1..file4' e, com um pouco de esforço, fiz:

for i in file*;do losetup -f ~/$i;done

numchunks=3
for i in `seq 0 $numchunks`; do
        sizeinsectors=$((`ls -l file$i | awk '{print $5}'`/512))
        startsector=$(($i*$sizeinsectors))
        echo "$startsector $sizeinsectors linear /dev/loop$i 0"
done | dmsetup create joined

Em seguida, / dev / dm-0 contém um dispositivo de bloco virtual com seu arquivo como conteúdo.

Eu não testei isso bem.

Outra edição: o tamanho do arquivo deve ser divisível uniformemente por 512 ou você perderá alguns dados. Se for, então você é bom. Vejo que ele também observou isso abaixo.

Rob Bos
fonte
É uma ótima idéia ler este arquivo uma vez, infelizmente não tem capacidade de saltar mais de quinze vezes para trás / para frente, não é?
apressar
7
@rush A alternativa superior pode ser colocar um dispositivo de loop em cada arquivo e combiná-los através dmsetupde um dispositivo de bloco virtual (que permite operações normais de busca, mas nem anexa nem truncada). Se o tamanho do primeiro arquivo não for múltiplo de 512, copie o último setor incompleto e os primeiros bytes do segundo arquivo (na soma 512) para um terceiro arquivo. O dispositivo de loop para o segundo arquivo precisaria --offsetentão.
Hauke ​​Laging
soluções elegantes. +1 também a Hauke ​​Laging, que sugere uma maneira de solucionar o problema se o tamanho do primeiro arquivo não for múltiplo de 512
Olivier Dulac
9

Você precisará escrever algo que copie dados em grupos que sejam no máximo tão grandes quanto a quantidade de espaço livre disponível. Deve funcionar assim:

  • Leia um bloco de dados de file2(usando pread()procurando antes da leitura no local correto).
  • Anexe o bloco a file1.
  • Use fcntl(F_FREESP)para desalocar o espaço de file2.
  • Repetir
Celada
fonte
11
Eu sei ... mas eu não conseguia pensar em nenhuma maneira que não envolvesse escrever código, e achei que escrever o que escrevi era melhor do que escrever nada. Não pensei no seu truque inteligente de começar do fim!
23913 Celada
O seu também não funcionaria sem começar do final, funcionaria?
Hauke ​​Laging
Não, ele funciona desde o início, pelo fcntl(F_FREESP)que libera o espaço associado a um determinado intervalo de bytes do arquivo (o torna escasso).
23913 Celada
Isso é bem legal. Mas parece ser um recurso muito novo. Não é mencionado na minha fcntlpágina do manual (15-04-2012).
Hauke ​​Laging
4
@HaukeLaging F_FREESP é o Solaris. No Linux (desde 2.6.38), é o sinalizador FALLOC_FL_PUNCH_HOLE do syscall fallocate. As versões mais recentes do utilitário fallocate util-linuxtêm uma interface para isso.
Stéphane Chazelas
0

Eu sei que é mais uma solução alternativa do que você solicitou, mas isso resolveria o seu problema (e com pouca fragmentação ou arranhão na cabeça):

#step 1
mount /path/to/... /the/new/fs #mount a new filesystem (from NFS? or an external usb disk?)

e depois

#step 2:
cat file* > /the/new/fs/fullfile

ou, se você acha que a compactação ajudaria:

#step 2 (alternate):
cat file* | gzip -c - > /the/new/fs/fullfile.gz

Então (e SOMENTE então), finalmente

#step 3:
rm file*
mv /the/new/fs/fullfile  .   #of fullfile.gz if you compressed it
Olivier Dulac
fonte
Infelizmente, o disco usb externo requer acesso físico e o nfs requer hardware adicional e eu não tenho nada disso. De qualquer forma obrigado. =)
rush
Eu pensei que seria assim ... A resposta de Rob Bos é, então, o que parece a melhor opção (sem o risco de perda de dados truncando-enquanto-cópia, e sem bater limitações FS também)
Olivier Dulac