Ajouter des fichiers énormes les uns aux autres sans les copier

41

Il y a 5 fichiers énormes (fichier1, fichier2, .. fichier5) environ 10G chacun et très peu d'espace libre sur le disque et j'ai besoin de concaténer tous ces fichiers en un seul. Il n'est pas nécessaire de conserver les fichiers originaux, mais uniquement les fichiers finaux.

La concaténation habituelle va avec caten séquence pour les fichiers file2.. file5:

cat file2 >> file1 ; rm file2

Malheureusement, cette façon nécessite au moins 10G d'espace libre que je n'ai pas. Existe-t-il un moyen de concaténer des fichiers sans les copier, mais en indiquant au système de fichiers que le fichier1 ne se termine pas à la fin du fichier original1 et continue au début du fichier2?

ps. Le système de fichiers est ext4 si cela compte.

se ruer
la source
2
Je serais intéressé de voir une solution, mais je soupçonne que ce n'est pas possible sans jouer directement avec le système de fichiers.
Kevin
1
Pourquoi avez-vous besoin d’un seul fichier physique aussi volumineux? Je pose la question parce que vous pouvez peut-être éviter de concaténer — ce qui, comme le montrent les réponses actuelles, est assez gênant.
Liori
6
@rush: alors cette réponse pourrait aider: serverfault.com/a/487692/16081
liori
1
Une alternative au device-mapper, moins efficace, mais plus facile à implémenter et qui aboutit à un périphérique partitionnable et utilisable à partir d’une machine distante, consiste à utiliser le mode "multi" de nbd-server.
Stéphane Chazelas
1
Ils me traitent toujours de stupide quand je leur dis que je pense que ça devrait être cool.
n611x007

Réponses:

19

Autant que je sache, il n’est (malheureusement) pas possible de tronquer un fichier depuis le début (cela peut être vrai pour les outils standard mais pour le niveau appel système, voir ici ). Mais en ajoutant une certaine complexité, vous pouvez utiliser la troncature normale (avec les fichiers fragmentés): vous pouvez écrire à la fin du fichier cible sans avoir écrit toutes les données entre les deux.

Supposons d’abord que les deux fichiers ont exactement 5 Gio (5120 Mio) et que vous souhaitez déplacer 100 Mio à la fois. Vous exécutez une boucle qui consiste en

  1. copie d'un bloc de la fin du fichier source à la fin du fichier cible (augmentation de l'espace disque utilisé)
  2. tronquer le fichier source d'un bloc (libérer de l'espace disque)

    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
    

Mais essayez d'abord avec des fichiers de test plus petits, s'il vous plaît ...

Les fichiers ne sont probablement ni de la même taille ni des multiples de la taille du bloc. Dans ce cas, le calcul des compensations devient plus compliqué. seek_byteset skip_bytesdevrait être utilisé alors.

Si c'est ce que vous voulez faire mais que vous avez besoin d'aide pour les détails, demandez à nouveau.

Attention

En fonction de la ddtaille du bloc, le fichier résultant sera un cauchemar de fragmentation.

Hauke ​​Laging
la source
On dirait que c'est le moyen le plus acceptable de concaténer des fichiers. Merci du conseil.
Rush
3
s'il n'y a pas de support de fichiers clairsemés alors vous pourriez par bloc inverse le second fichier en place, puis retirez simplement le dernier bloc et de l' ajouter au second fichier
freak cliquet
1
Je ne l'ai pas essayé moi-même (bien que je sois sur le point de le faire), mais seann.herdejurgen.com/resume/samag.com/html/v09/i08/a9_l1.htm est un script Perl qui prétend implémenter cet algorithme.
dimanche
16

Au lieu de regrouper les fichiers en un seul fichier, simulez peut-être un seul fichier avec un canal nommé, si votre programme ne peut pas gérer plusieurs fichiers.

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

Comme le suggère Hauke, losetup / dmsetup peut également fonctionner. Une expérience rapide; J'ai créé 'file1..file4' et avec un peu d'effort, j'ai:

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

Ensuite, / dev / dm-0 contient un périphérique de bloc virtuel avec votre fichier en tant que contenu.

Je n'ai pas bien testé.

Autre édition: la taille du fichier doit être divisible par 512. Dans le cas contraire, vous perdrez certaines données. Si c'est le cas, ça va. Je vois qu'il a également noté cela ci-dessous.

Rob Bos
la source
C'est une bonne idée de lire ce fichier une fois. Malheureusement, il n'a pas la capacité de passer de fifo en arrière / en avant, n'est-ce pas?
Rush
7
@rush L'alternative supérieure peut être de placer un périphérique de boucle sur chaque fichier et de les combiner via dmsetupun périphérique de bloc virtuel (ce qui permet des opérations de recherche normales mais ni les ajouts ni les tronques). Si la taille du premier fichier n'est pas un multiple de 512, vous devez copier le dernier secteur incomplet et les premiers octets du deuxième fichier (somme 512) dans un troisième fichier. Le périphérique de boucle pour le second fichier aurait --offsetalors besoin .
Hauke ​​Laging
solutions élégantes. +1 également à Hauke ​​Laging qui suggère un moyen de contourner le problème si la taille du / des premier (s) fichier (s) n'est pas un multiple de 512
Olivier Dulac
9

Vous devrez écrire quelque chose qui copie les données dans des groupes dont la taille est au plus égale à la quantité d'espace libre dont vous disposez. Cela devrait fonctionner comme ceci:

  • Lire un bloc de données à partir de file2(utiliser pread()en cherchant avant la lecture au bon endroit).
  • Ajouter le bloc à file1.
  • Utilisez fcntl(F_FREESP)pour désallouer l'espace de file2.
  • Répéter
Celada
la source
1
Je sais ... mais je ne pouvais penser à aucune façon qui n'implique pas d'écrire du code, et j'ai pensé que l'écriture de ce que j'avais écrit était préférable à l'écriture de rien. Je n'ai pas pensé à votre astuce de départ à partir de la fin!
Celada
Les vôtres aussi ne travailleraient pas sans partir de la fin, n'est-ce pas?
Hauke ​​Laging
Non, cela fonctionne depuis le début car fcntl(F_FREESP)cela libère l'espace associé à une plage d'octets donnée du fichier (cela le rend clairsemé).
Celada
C'est plutôt cool. Mais semble être une fonctionnalité très nouvelle. Il n'est pas mentionné dans ma fcntlpage de manuel (2012-04-15).
Hauke ​​Laging
4
@HaukeLaging F_FREESP est celui de Solaris. Sous Linux (depuis la version 2.6.38), il s’agit de l’indicateur FALLOC_FL_PUNCH_HOLE de l’ fallocateappel système. Les versions les plus récentes de l'utilitaire fallocate util-linuxont une interface pour cela.
Stéphane Chazelas
0

Je sais que c'est plus une solution de contournement que ce que vous avez demandé, mais cela réglerait votre problème (et avec un minimum de fragmentation ou de blessure à la tête):

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

et alors

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

ou, si vous pensez que la compression aiderait:

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

Alors (et UNIQUEMENT alors), enfin

#step 3:
rm file*
mv /the/new/fs/fullfile  .   #of fullfile.gz if you compressed it
Olivier Dulac
la source
Malheureusement, le disque USB externe nécessite un accès physique et NFS nécessite du matériel supplémentaire et je n'en ai aucun. En tout cas merci. =)
précipiter
Je pensais que ce serait ainsi ... La réponse de Rob Bos est alors ce qui semble être votre meilleure option (sans risquer de perdre des données en tronquant pendant la copie, et sans frapper également les limitations de FS)
Olivier Dulac