
Eu tenho um despejo de milhares de pequenos arquivos de texto (1-5 MB), cada um contendo linhas de texto. Preciso "agrupar" eles, para que cada lote tenha um tamanho fixo - digamos 100 MB, e compactar esse lote.
Agora esse lote poderia ser:
- Um único arquivo que é apenas um 'gato' do conteúdo dos arquivos de texto individuais, ou
- Apenas os próprios arquivos de texto individuais
Ressalvas:
- unix
split -b
não funcionará aqui porque preciso manter as linhas de texto intactas. Usar alines
opção é um pouco complicado porque há uma grande variação no número de bytes em cada linha. - Os arquivos não precisam ter um tamanho estritamente fixo, desde que estejam dentro de 5% do tamanho solicitado
- As linhas são críticas e não devem ser perdidas: preciso confirmar se a entrada chegou à saída sem perdas - qual soma de verificação contínua (algo como CRC32, MAS melhor/"mais forte" em face de colisões)
Um script deve funcionar bem, mas parece uma tarefa que alguém já fez antes, e seria bom ver algum código (de preferência python ou ruby) que fizesse pelo menos algo semelhante.
Responder1
O script a seguir criará pacotes compactados de arquivos dentro de um determinado diretório. Você o invoca como:
script.sh directorypath
Ele usa um algoritmo simples/ingênuo (como o que você descreveu):
- Conte todos os arquivos.
- Comece a ler todos os arquivos recursivamente.
- Obtenha tamanho, entrada ls e soma de verificação (SHA1) de cada arquivo. (Armazene a entrada ls em manifest.txt e checksum em checksum.txt).
- Se o tamanho acabou de ultrapassar o limite, use a lista manifest.txt para criar um tar compactado (que também inclui os arquivos de manifesto e de soma de verificação).
- Crie um diretório temporário e descompacte o tar recém-criado.
- Calcule novas somas de verificação de arquivos recém-extraídos.
- Compare com somas de verificação armazenadas.
- Se pelo menos uma soma de verificação for diferente, saia com um erro.
- Caso contrário, verifique se a opção de exclusão está habilitada; em caso afirmativo, remova os arquivos de origem.
- Repita até que não haja mais arquivos.
Sua saída é 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...
Os arquivos do pacote são criados no diretório atual.
Um aviso:
- o diretório atual e o diretório de origem não devem ser pai/filho ou filho/pai, não foi testado dessa forma.
Este é o script (O PACKSIZELIMIT é o número de bytes, se DELETESOURCE for 1, ele excluirá os arquivos de origem [bem, você também deve remover o símbolo # antes da linha "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
Responder2
A divisão GNU tem uma -C
opção semelhante, -b
mas não quebra linhas.