Anexa arquivos enormes uns aos outros sem copiá-los

39

Existem 5 arquivos enormes (file1, file2, .. file5) sobre 10G cada e espaço livre extremamente baixo deixado no disco e eu preciso concatenar todos esses arquivos em um. Não há necessidade de manter arquivos originais, apenas o final.

A concatenação usual está em andamento com cat dos arquivos file2 .. file5 :

cat file2 >> file1 ; rm file2

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

ps. sistema de arquivos é ext4 se isso importa.

    
por rush 23.06.2013 / 17:39

4 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 syscall veja aqui ). Mas, adicionando alguma complexidade, você pode usar o truncamento normal (junto com arquivos esparsos): Você pode escrever no final do arquivo de destino sem ter escrito todos os dados entre eles.

Vamos assumir primeiro que ambos os arquivos sejam exatamente 5GiB (5120 MiB) e que você queira 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. truncando 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 experimente primeiro com arquivos de teste menores, por favor ...

Provavelmente, os arquivos não são do mesmo tamanho nem múltiplos do tamanho do bloco. Nesse caso, o cálculo das compensações torna-se mais complicado. seek_bytes e skip_bytes devem ser usados.

Se esta é a maneira que você quer ir, mas precisar de ajuda para os detalhes, pergunte novamente.

Aviso

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

    
por 23.06.2013 / 18:03
15

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

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

Como Hauke sugere, o losetup / dmsetup também pode funcionar. Uma experiência rápida; Eu 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 bem isso.

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

    
por 23.06.2013 / 18:51
9

Você terá que 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 para o local correto).
  • Anexe o bloco a file1 .
  • Use fcntl(F_FREESP) para desalocar o espaço de file2 .
  • Repetir
por 23.06.2013 / 18:02
0

Eu sei que é mais uma solução alternativa do que o que você pediu, mas ele cuidaria do seu problema (e com pouca fragmentação ou headscratch):

#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
    
por 24.06.2013 / 19:51