Agregue archivos enormes entre sí sin copiarlos

41

Hay 5 archivos enormes (archivo1, archivo2, ... archivo5) de aproximadamente 10G cada uno y un espacio libre extremadamente bajo que queda en el disco y necesito concatenar todos estos archivos en uno. No es necesario conservar los archivos originales, solo el final.

La concatenación habitual va caten secuencia para los archivos file2... file5:

cat file2 >> file1 ; rm file2

Desafortunadamente, esta forma requiere un espacio libre de al menos 10G que no tengo. ¿Hay alguna manera de concatenar archivos sin copiarlo realmente pero decirle al sistema de archivos de alguna manera que el archivo1 no termina en el archivo original1 y continúa al inicio del archivo2?

PD. el sistema de archivos es ext4 si eso importa.

prisa
fuente
2
Me interesaría ver una solución, pero sospecho que no es posible sin jugar directamente con el sistema de archivos.
Kevin
1
¿Por qué necesita tener un solo archivo físico que es tan grande? Lo pregunto porque tal vez puedas evitar la concatenación, lo que, como muestran las respuestas actuales, es bastante molesto.
liori
66
@rush: entonces esta respuesta podría ayudar: serverfault.com/a/487692/16081
liori
1
Una alternativa al mapeador de dispositivos, menos eficiente, pero más fácil de implementar y resulta en un dispositivo particionable y puede usarse desde una máquina remota, es usar el modo "multi" nbd-server.
Stéphane Chazelas
1
Siempre me llaman estúpido cuando digo que creo que esto debería ser genial.
n611x007

Respuestas:

19

AFAIK (desafortunadamente) no es posible truncar un archivo desde el principio (esto puede ser cierto para las herramientas estándar pero para el nivel de syscall ver aquí ). Pero al agregar cierta complejidad, puede usar el truncamiento normal (junto con archivos dispersos): puede escribir al final del archivo de destino sin haber escrito todos los datos intermedios.

Supongamos primero que ambos archivos son exactamente 5GiB (5120 MiB) y que desea mover 100 MiB a la vez. Ejecutas un bucle que consiste en

  1. copiar un bloque desde el final del archivo de origen hasta el final del archivo de destino (aumentando el espacio de disco consumido)
  2. truncar el archivo fuente en un bloque (liberando espacio en 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
    

Pero pruébelo primero con archivos de prueba más pequeños, por favor ...

Probablemente los archivos no sean del mismo tamaño ni múltiplos del tamaño del bloque. En ese caso, el cálculo de las compensaciones se vuelve más complicado. seek_bytesy skip_bytesdebe usarse entonces.

Si es así, pero necesita ayuda para los detalles, pregunte nuevamente.

Advertencia

Dependiendo del ddtamaño del bloque, el archivo resultante será una pesadilla de fragmentación.

Hauke ​​Laging
fuente
Parece que esta es la forma más aceptable de concatenar archivos. Gracias por el consejo.
precipitarse
3
si no hay apoyo archivo disperso entonces se podría bloquear en cuanto a revertir el segundo archivo en su lugar y luego se acaba de quitar el último bloque y añadirlo al archivo segundos
monstruo de trinquete
1
No lo he intentado yo mismo (aunque estoy a punto de hacerlo), pero seann.herdejurgen.com/resume/samag.com/html/v09/i08/a9_l1.htm es un script de Perl que afirma implementar este algoritmo.
zwol
16

En lugar de agrupar los archivos en un solo archivo, tal vez simule un solo archivo con una tubería con nombre, si su programa no puede manejar múltiples archivos.

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

Como sugiere Hauke, losetup / dmsetup también pueden funcionar. Un experimento rápido Creé 'file1..file4' y con un poco de esfuerzo, hice:

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

Luego, / dev / dm-0 contiene un dispositivo de bloque virtual con su archivo como contenido.

No he probado esto bien.

Otra edición: el tamaño del archivo debe ser divisible de manera uniforme por 512 o perderá algunos datos. Si es así, entonces eres bueno. Veo que también notó eso a continuación.

Rob Bos
fuente
Es una buena idea leer este archivo una vez, desafortunadamente no tiene la capacidad de saltar más de quince hacia atrás / adelante, ¿no?
precipitarse
77
@rush La alternativa superior puede ser colocar un dispositivo de bucle en cada archivo y combinarlos a través dmsetupde un dispositivo de bloque virtual (que permite operaciones de búsqueda normales pero no se agrega ni se trunca). Si el tamaño del primer archivo no es múltiplo de 512, debe copiar el último sector incompleto y los primeros bytes del segundo archivo (en suma 512) a un tercer archivo. El dispositivo de bucle para el segundo archivo necesitaría --offsetentonces.
Hauke ​​Laging
soluciones elegantes +1 también a Hauke ​​Laging, que sugiere una forma de solucionar el problema si el tamaño del primer archivo no es un múltiplo de 512
Olivier Dulac
9

Tendrá que escribir algo que copie datos en grupos que sean como máximo tan grandes como la cantidad de espacio libre que tenga. Debería funcionar así:

  • Leer un bloque de datos de file2 (utilizando pread()mediante la búsqueda antes de la lectura en la ubicación correcta).
  • Añadir el bloque a file1.
  • Utilizar fcntl(F_FREESP) para desasignar el espacio desde file2.
  • Repetir
Celada
fuente
1
Lo sé ... pero no podía pensar en ninguna forma que no implicara escribir código, y pensé que escribir lo que escribía era mejor que no escribir nada. ¡No pensé en tu ingenioso truco de comenzar desde el final!
Celada
El tuyo tampoco funcionaría sin comenzar desde el final, ¿verdad?
Hauke ​​Laging
No, funciona desde el principio debido a fcntl(F_FREESP)que libera el espacio asociado con un rango de bytes dado del archivo (lo hace escaso).
Celada
Eso es muy bonito. Pero parece ser una característica muy nueva. No se menciona en mi fcntlpágina de manual (2012-04-15).
Hauke ​​Laging
44
@HaukeLaging F_FREESP es el de Solaris. En Linux (desde 2.6.38), es el indicador FALLOC_FL_PUNCH_HOLE de la fallocatellamada al sistema. Las versiones más nuevas de la utilidad Falocate util-linuxtienen una interfaz para eso.
Stéphane Chazelas
0

Sé que es más una solución alternativa de lo que pediste, pero resolvería tu problema (y con poca fragmentación o rasguño):

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

y entonces

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

o, si crees que la compresión ayudaría:

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

Entonces (y SOLO entonces), finalmente

#step 3:
rm file*
mv /the/new/fs/fullfile  .   #of fullfile.gz if you compressed it
Olivier Dulac
fuente
Desafortunadamente, el disco USB externo requiere acceso físico y nfs requiere hardware adicional y no tengo nada de eso. Gracias de todos modos. =)
rush
Pensé que sería así ... La respuesta de Rob Bos es, entonces, lo que parece su mejor opción (sin arriesgarse a perder datos truncando mientras se copia, y sin afectar también las limitaciones de FS)
Olivier Dulac