我有一个包含数千个小文本文件(1-5MB)的转储,每个文件都包含几行文本。我需要将它们“批量”处理,以便每个批次的大小固定 - 比如 100MB,然后压缩该批次。
现在该批次可能是:
- 单个文件只是各个文本文件内容的“cat”,或者
- 仅单个文本文件本身
注意事项:
- unix
split -b
在这里不起作用,因为我需要保持文本行完整。使用该lines
选项有点复杂,因为每行的字节数差异很大。 - 文件大小不必严格固定,只要在请求大小的 5% 以内即可
- 这些线很关键,不能丢失:我需要确认输入无损地到达输出 - 滚动校验和(类似于 CRC32,但在发生碰撞时更好/“更强”)
脚本应该可以很好地完成,但是这看起来像是某人以前做过的任务,并且如果能看到一些至少能完成类似任务的代码(最好是 python 或 ruby)就好了。
答案1
以下脚本将在给定目录中创建文件的压缩包。您可以像这样调用它:
script.sh directorypath
它使用一个简单/朴素的算法(就像你描述的一样):
- 计数所有文件。
- 开始递归读取所有文件。
- 获取每个文件的大小、ls 条目和校验和 (SHA1)。(将 ls 条目存储在 manifest.txt 中,将校验和存储在 checksum.txt 中)。
- 如果大小刚刚超过限制,请使用 manifest.txt 列表创建压缩的 tar(其中也包括清单和校验和文件)。
- 创建临时目录并解压缩刚刚创建的 tar。
- 计算刚刚提取的文件的新校验和。
- 与存储的校验和进行比较。
- 如果至少有一个校验和不同,则退出并显示错误。
- 否则,检查删除选项是否启用,如果是,则删除源文件。
- 重复,直到没有更多文件。
其输出如下:
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...
包文件在当前目录中创建。
一个警告:
- 当前目录和源目录不应该是父/子或子/父,尚未以这种方式进行测试。
这是脚本(PACKSIZELIMIT 是字节数,如果 DELETESOURCE 是 1,那么它将删除源文件[好吧,您还应该删除“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
答案2
GNU split 有一个-C
选项,类似-b
但不换行。