Buscando una herramienta/script de Unix que, dada una ruta de entrada, comprimirá cada lote de archivos de texto de 100 MB sin comprimir en un solo archivo gzip

Buscando una herramienta/script de Unix que, dada una ruta de entrada, comprimirá cada lote de archivos de texto de 100 MB sin comprimir en un solo archivo gzip

Tengo un volcado de miles de archivos de texto pequeños (de 1 a 5 MB) de tamaño, cada uno de los cuales contiene líneas de texto. Necesito "hacer lotes" para que cada lote tenga un tamaño fijo, digamos 100 MB, y comprimir ese lote.

Ahora ese lote podría ser:

  1. Un único archivo que es sólo un 'gato' del contenido de los archivos de texto individuales, o
  2. Sólo los archivos de texto individuales

Advertencias:

  1. Unix split -bno funcionará aquí porque necesito mantener intactas las líneas de texto. Usar esta linesopción es un poco complicado ya que existe una gran variación en el número de bytes en cada línea.
  2. Los archivos no necesitan tener un tamaño estrictamente fijo, siempre y cuando estén dentro del 5% del tamaño solicitado.
  3. Las líneas son críticas y no deben perderse: necesito confirmar que la entrada llegó a la salida sin pérdidas: qué suma de verificación continua (algo como CRC32, PERO mejor/"más fuerte" frente a colisiones)

Un script debería funcionar bien, pero parece una tarea que alguien ha realizado antes, y sería bueno ver algún código (preferiblemente Python o Ruby) que haga al menos algo similar.

Respuesta1

El siguiente script creará paquetes comprimidos de archivos dentro del directorio dado. Lo invocas como:

script.sh directorypath

Utiliza un algoritmo simple/ingenuo (como el que describiste):

  • Cuente todos los archivos.
  • Comience a leer todos los archivos de forma recursiva.
  • Obtenga el tamaño, la entrada ls y la suma de comprobación (SHA1) de cada archivo. (Almacene la entrada ls en manifest.txt y la suma de comprobación en checksum.txt).
  • Si el tamaño acaba de superar el límite, utilice la lista manifest.txt para crear un archivo tar comprimido (que también incluye los archivos de manifiesto y de suma de comprobación).
  • Cree un directorio temporal y descomprima el tar recién creado.
  • Calcule nuevas sumas de comprobación de archivos recién extraídos.
  • Comparar con sumas de verificación almacenadas.
  • Si al menos una suma de comprobación es diferente, salga con un error.
  • De lo contrario, verifique si la opción de eliminación está habilitada; de ser así, elimine los archivos fuente.
  • Repita hasta que no haya más archivos.

Su salida es como:

Reading files...
15898 849 ../out/f068715p.jpg
Creating package (pack18.tar.gz) with 849 files, using 100078420 bytes...
tar: Removing leading `../' from member names
Preparing to verify package...
Creating new checksums...
Comparing checksums...
Package verification OK.
Deleting temporary verification directory...
Reading files...
16731 833 ../out/f069111c.jpg
Creating package (pack19.tar.gz) with 833 files, using 100004735 bytes...
tar: Removing leading `../' from member names
Preparing to verify package...
Creating new checksums...
Comparing checksums...
Package verification OK.
Deleting temporary verification directory...
Reading files...

Los archivos del paquete se crean en el directorio actual.

Una advertencia:

  • el directorio actual y el directorio de origen no deben ser padre/hijo o hijo/padre, no se ha probado de esa manera.

Este es el script (PACKSIZELIMIT es el número de bytes, si DELETESOURCE es 1, entonces eliminará los archivos fuente [bueno, también debes eliminar el símbolo # antes de la línea "rm -f"]):

#!/bin/bash

PACKSIZELIMIT=100000000
PACKPREFFIX="pack"
#PACKSUFFIX=$(date +"_%Y%m%d_%H%M")
PACKSUFFIX=""
DELETESOURCE=0


LISTFILE="packbatchlist.txt"
MANIFESTFILE="manifest.txt"
CHECKSUMFILE="checksums.txt"
VERIFYFILE="verifysums.txt"



if [ -d "$1" ]; then
  PACKCOUNTER=0
  PACKSIZE=0
  FILECOUNTER=0
  ALLFILECOUNTER=0
  cat /dev/null > $LISTFILE
  cat /dev/null > $MANIFESTFILE
  cat /dev/null > $CHECKSUMFILE
  cat /dev/null > $VERIFYFILE
  echo "Reading files..."
  TOTALFILES=$(find "$1" -type f | wc -l)
  echo "There are $TOTALFILES files to process..."
  find "$1" -type f | while read SOURCEFILE ; do
    let "FILECOUNTER+=1"
    let "ALLFILECOUNTER+=1"
    echo -ne "\r$ALLFILECOUNTER $FILECOUNTER $SOURCEFILE\e[K"
    THISFILESIZE=$(stat -c %s "$SOURCEFILE")
    let "PACKSIZE+=$THISFILESIZE"
    echo $SOURCEFILE >> $LISTFILE
    ls -l $SOURCEFILE >> $MANIFESTFILE
    sha1sum $SOURCEFILE >> $CHECKSUMFILE
    if [ $PACKSIZE -gt $PACKSIZELIMIT -o $ALLFILECOUNTER -eq $TOTALFILES ]; then
      echo
      echo $MANIFESTFILE >> $LISTFILE
      echo $CHECKSUMFILE >> $LISTFILE
      PACKFILENAME="$PACKPREFFIX$PACKCOUNTER$PACKSUFFIX.tar.gz"
      echo "Creating package ($PACKFILENAME) with $FILECOUNTER files, using $PACKSIZE bytes..."
      tar -cf - -T $LISTFILE | gzip -c > $PACKFILENAME
      echo "Preparing to verify package..."
      TEMPCHECKDIR=$(mktemp -d)
      tar xzf $PACKFILENAME -C $TEMPCHECKDIR
      if [ -r "$TEMPCHECKDIR/$CHECKSUMFILE" ] ; then
        cut -d " " -f 1 $TEMPCHECKDIR/$CHECKSUMFILE > $VERIFYFILE
        sort $VERIFYFILE > $TEMPCHECKDIR/$CHECKSUMFILE
        echo "Creating new checksums..."
        cat /dev/null > $VERIFYFILE
        find "$TEMPCHECKDIR" -type f | while read CHECKEDFILE ; do
          CHECKEDFILESHORT=$(basename $CHECKEDFILE)
          if [ "$CHECKEDFILESHORT" != "$MANIFESTFILE" -a "$CHECKEDFILESHORT" != "$CHECKSUMFILE" ] ; then
            sha1sum $CHECKEDFILE | cut -d " " -f 1 >> $VERIFYFILE
          fi
        done
        sort $VERIFYFILE -o $VERIFYFILE
        echo "Comparing checksums..."
        DIFFFILES=$(comm --nocheck-order -3 $TEMPCHECKDIR/$CHECKSUMFILE $VERIFYFILE | wc -l)
        if [ $DIFFFILES -gt 0 ] ; then
          echo "ERROR: Package failed verification!"
          exit 2
        else
          echo "Package verification OK."
          echo "Deleting temporary verification directory..."
          find "$TEMPCHECKDIR" -delete
          if [ "$DELETESOURCE" == "1" ] ; then
            echo "Deleting source files..."
            cat $LISTFILE | while read FILE2DEL ; do
              echo -ne "\rDeleting $FILE2DEL ... \e[K"
              #rm -f $FILE2DEL
            done
            echo -e "\rFiles deleted.\e[K"
          fi
        fi
      else
        echo "ERROR: Cannot find checksum file from package!"
        exit 1
      fi
      let "PACKCOUNTER+=1"
      PACKSIZE=0
      FILECOUNTER=0
      cat /dev/null > $LISTFILE
      cat /dev/null > $MANIFESTFILE
      cat /dev/null > $CHECKSUMFILE
      cat /dev/null > $VERIFYFILE
      echo "Reading files..."
    fi
  done
else 
  echo
  echo "Missing source directory"
  echo
fi   


rm -f $LISTFILE
rm -f $MANIFESTFILE
rm -f $CHECKSUMFILE
rm -f $VERIFYFILE

Respuesta2

GNU split tiene una -Copción que es similar -bpero no rompe líneas.

información relacionada