Aggiungi file enormi l'uno all'altro senza copiarli


41

Ci sono 5 file enormi (file1, file2, .. file5) circa 10 G ciascuno e uno spazio libero estremamente basso rimasto sul disco e ho bisogno di concatenare tutti questi file in uno. Non è necessario conservare i file originali, solo l'ultimo.

La solita concatenazione procede catin sequenza per i file file2.. file5:

cat file2 >> file1 ; rm file2

Purtroppo in questo modo richiede almeno 10 G di spazio libero che non ho. C'è un modo per concatenare i file senza copiarli, ma in qualche modo dire al filesystem che file1 non termina alla fine di file1 originale e continua all'avvio di file2?

ps. il filesystem è ext4 se questo è importante.


2
Sarei interessato a vedere una soluzione, ma sospetto che non sia possibile senza scherzare direttamente con il filesystem.
Kevin,

1
Perché è necessario disporre di un singolo file fisico così grande? Lo sto chiedendo perché forse puoi evitare di concatenare, il che, come mostrano le risposte attuali, è piuttosto fastidioso.
liori,

6
@rush: questa risposta potrebbe essere d'aiuto: serverfault.com/a/487692/16081
liori

1
Alternativa al device-mapper, meno efficiente, ma più facile da implementare e si traduce in un dispositivo partizionabile e che può essere usato da una macchina remota è usare la modalità "multi" di nbd-server.
Stéphane Chazelas,

1
Mi chiamano sempre stupidi quando dico che penso che dovrebbe essere bello.
n611x007,

Risposte:


19

AFAIK non è (purtroppo) possibile troncare un file dall'inizio (questo può essere vero per gli strumenti standard ma per il livello di syscall vedere qui ). Ma con l'aggiunta di una certa complessità è possibile utilizzare il troncamento normale (insieme a file sparsi): è possibile scrivere alla fine del file di destinazione senza aver scritto tutti i dati in mezzo.

Supponiamo che entrambi i file siano esattamente 5GiB (5120 MiB) e che tu voglia spostare 100 MiB alla volta. Esegui un ciclo composto da

  1. copiando un blocco dalla fine del file sorgente alla fine del file di destinazione (aumentando lo spazio su disco consumato)
  2. troncare il file sorgente di un blocco (liberando spazio su 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
    

Ma prima prova con file di test più piccoli, per favore ...

Probabilmente i file non hanno né la stessa dimensione né multipli della dimensione del blocco. In tal caso, il calcolo degli offset diventa più complicato. seek_byteseskip_bytes dovrebbe essere usato allora.

Se questo è il modo in cui vuoi andare ma hai bisogno di aiuto per i dettagli, chiedi di nuovo.

avvertimento

A seconda della dddimensione del blocco, il file risultante sarà un incubo di frammentazione.


Sembra che questo sia il modo più accettabile per concatenare i file. Grazie per il consiglio.
corsa il

3
se non esiste un supporto per file sparsi, è possibile invertire a blocchi il secondo file in atto, quindi rimuovere l'ultimo blocco e aggiungerlo al secondo file
maniaco del cricchetto

1
Non ho provato questo da solo (anche se sto per farlo), ma seann.herdejurgen.com/resume/samag.com/html/v09/i08/a9_l1.htm è uno script Perl che afferma di implementare questo algoritmo.
zwol,

16

Invece di raggruppare i file in un unico file, forse simula un singolo file con una pipe denominata, se il tuo programma non è in grado di gestire più file.

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

Come suggerisce Hauke, anche losetup / dmsetup può funzionare. Un rapido esperimento; Ho creato 'file1..file4' e con un po 'di sforzo ho fatto:

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

Quindi, / dev / dm-0 contiene un dispositivo a blocchi virtuali con il file come contenuto.

Non l'ho provato bene.

Un'altra modifica: la dimensione del file deve essere divisibile uniformemente per 512 o perderai alcuni dati. Se lo è, allora sei bravo. Vedo che ha anche notato che di seguito.


È una buona idea leggere questo file una volta, sfortunatamente non ha la possibilità di saltare oltre cinquanta avanti / indietro, non è vero?
corsa il

7
@rush L'alternativa migliore potrebbe essere quella di mettere un dispositivo loop su ciascun file e combinarli tramite dmsetupun dispositivo a blocchi virtuale (che consente le normali operazioni di ricerca ma né accodare né troncare). Se la dimensione del primo file non è un multiplo di 512, è necessario copiare l'ultimo settore incompleto e i primi byte dal secondo file (in somma 512) in un terzo file. Il dispositivo loop per il secondo file avrebbe bisogno --offsetquindi.
Hauke ​​Laging,

soluzioni eleganti. +1 anche a Hauke ​​Laging che suggerisce un modo per aggirare il problema se la dimensione del primo file non è un multiplo di 512
Olivier Dulac

9

Dovrai scrivere qualcosa che copi i dati in gruppi grandi al massimo quanto la quantità di spazio libero che hai. Dovrebbe funzionare così:

  • Leggi un blocco di dati da file2(usando pread()cercando prima della lettura nella posizione corretta).
  • Aggiungi il blocco a file1.
  • Utilizzare fcntl(F_FREESP)per deallocare lo spazio da file2.
  • Ripetere

1
Lo so ... ma non riuscivo a pensare a nessun modo che non comportasse la scrittura di codice, e ho pensato che scrivere ciò che ho scritto fosse meglio che non scrivere nulla. Non ho pensato al tuo trucco intelligente di partire dalla fine!
Celada,

Anche il tuo non funzionerebbe senza partire dalla fine, vero?
Hauke ​​Laging,

No, funziona dall'inizio a causa della fcntl(F_FREESP)quale libera lo spazio associato a un determinato intervallo di byte del file (lo rende scarso).
Celada,

È abbastanza bello. Ma sembra essere una novità. Non è menzionato nella mia fcntlpagina man (15-04-2012).
Hauke ​​Laging,

4
@HaukeLaging F_FREESP è quello di Solaris. Su Linux (dal 2.6.38), è il flag FALLOC_FL_PUNCH_HOLE della fallocatesyscall. Le versioni più recenti dell'utilità fallocate util-linuxhanno un'interfaccia a quella.
Stéphane Chazelas,

0

So che è più una soluzione alternativa di quello che hai chiesto, ma si occuperebbe del tuo problema (e con poca frammentazione o headcratch):

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

e poi

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

o, se pensi che la compressione possa aiutare:

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

Quindi (e SOLO quindi), finalmente

#step 3:
rm file*
mv /the/new/fs/fullfile  .   #of fullfile.gz if you compressed it

Sfortunatamente il disco USB esterno richiede l'accesso fisico e nfs richiede hardware aggiuntivo e non ne ho nulla. Comunque grazie. =)
precipita il

Ho pensato che sarebbe stato così ... La risposta di Rob Bos è quella che sembra la tua migliore opzione (senza rischiare di perdere i dati troncando-mentre-copiando, e senza colpire anche le limitazioni di FS)
Olivier Dulac il
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.