将大目录树拆分为指定大小的块?

将大目录树拆分为指定大小的块?

我有一个目录树,我想将其备份到光盘上。不幸的是,它超过了任何一个磁盘的大小(大约 60GB)。我正在寻找一个脚本,可以将这棵树分成适当大小的块,并带有硬链接或诸如此类的东西(保持原始状态不变)。然后我可以将这些小型树输入到备份过程中(添加 PAR2 冗余等)。

这不是一个花哨的脚本,但看起来它可能已经完成了。建议?

(一步跨越和写入是不行的,因为我想在文件被烧毁之前做更多的事情。)

答案1

有一个为此设计的应用程序:dirsplit

它通常存在于cdrkitdirsplit包装中。

它可以创建带有链接的即用型文件夹,以便使用 K3b 或其他 GUI 软件轻松创建 DVD

答案2

你也可以尝试部分,我编写的一个工具(BSD 许可): https://sourceforge.net/projects/fpart/

答案3

我曾经为了类似的目的制作过一个丑陋的脚本。这只是一个拼凑,但当我写它时,我并不关心执行时间或美观性。我确信同一概念还有更多“产品化”版本,但如果您希望获得一些想法或一些东西来开始黑客攻击,这里是(2008 年做的,所以使用风险自负!):- )

#!/bin/sh -
REPO=/export/foton/PictureStore
LINKS=/export/foton/links
SPLITTIX=`date '+%y%m%d-%H%M'`

# kilobytes
DVDSIZE=4400000
PARTPREFIX="DVD-"
REPOSIZE=`du -sk -- ${REPO} | awk '{print $1}'`
NUMPARTS=`expr $REPOSIZE / $DVDSIZE`
SPLITDIR=${LINKS}/splits/${SPLITTIX}
mkdir -p -- "$SPLITDIR"

PARTNUM=1
PARTSIZ=0
DONESIZ=0
PARTNUM=`echo $PARTNUM | awk '{printf("%03x", $0)}'`
mkdir -p -- "${SPLITDIR}/${PARTPREFIX}${PARTNUM}"
for D in "${REPO}"/..?* "${REPO}"/.[!.]* "${REPO}"/*
do
  if [ ! -e "$D" ]; then continue; fi  # skip ..?*, .[!.]* and * if there are no matching files
  D=${D#$REPO/}
  D_SIZ=`du -sk -- "${REPO}/$D" | awk '{print $1}'`
  if test `expr $D_SIZ + $PARTSIZ` -le $DVDSIZE
  then
    # link to D in this part
    ln -s -- "$REPO/$D" "${SPLITDIR}/${PARTPREFIX}${PARTNUM}/$D"
    # adjust counters
    PARTSIZ=`expr $PARTSIZ + $D_SIZ`
    DONESIZ=`expr $DONESIZ + $D_SIZ`
  else
    # next part and link to D in that
    echo PART $PARTNUM: $PARTSIZ kb '(target' $DVDSIZE 'kb)'
    PARTNUM=`expr $PARTNUM + 1`
    PARTNUM=`echo $PARTNUM | awk '{printf("%03x", $0)}'`
    PARTSIZ=$D_SIZ
    DONESIZ=`expr $DONESIZ + $D_SIZ`
    mkdir -p -- "${SPLITDIR}/${PARTPREFIX}${PARTNUM}"
    ln -s -- "$REPO/$D" "${SPLITDIR}/${PARTPREFIX}${PARTNUM}/$D"
  fi
done
echo "wrote $DONESIZ kb in $PARTNUM parts in $SPLITDIR"

我想我已经通过 samba 将结果共享到从中刻录光盘的 Windows 主机。如果您使用上述未更改的内容,您可能希望使用mkisofs或其他解析符号链接的存档器。

答案4

我们不应该忘记,任务的本质确实很简单;正如 Haskell 教程中所述(围绕此任务的解决方案的工作编写,逐步完善)

现在让我们思考一下我们的程序将如何运行并用伪代码表达它:

main = Read list of directories and their sizes.
       Decide how to fit them on CD-Rs.
       Print solution.

听起来有道理吗?我是这么想的。

让我们稍微简化一下我们的生活,暂时假设我们将在程序之外的某个地方计算目录大小(例如,使用“ du -sb *”)并从标准输入中读取此信息。

(从Haskell 搭便车指南,第一章

(此外,在您的问题中,您希望能够调整(编辑)生成的磁盘布局,然后使用工具来刻录它们。)

您可以重新使用(改编和重新使用)Haskell 教程中的程序的简单变体来拆分文件集合。

不幸的是,在distribute我在另一个答案中提到的工具,基本拆分任务的简单性与用户界面的复杂性和臃肿程度不匹配distribute(因为它是为了组合多个任务而编写的;虽然分阶段执行,但仍然不是以我现在能想到的最干净的方式组合) 。

为了帮助您充分利用它的代码,这里是 bash 代码的摘录distribute(位于380线)用于执行分割文件集合的“基本”任务:

# Splitting:

function splitMirrorDir() {
  if [[ ! -d "$THIS_BASES_DIR/$BASE/$type" ]]; then
    echo $"No base fixed for $type" >&2
    exit 1
  fi

  # Getting the list of all suitable files:
  local -a allFiles
  let 'no = 0' ||:
  allFiles=()
  # no points to the next free position in allFiles
  # allFiles contains the constructed list
  for p in "$THIS_BASES_DIR/$BASE/$type"/*.rpm; do
      if [[ ! -e "$p" ]]; then
      # fail on non-existent files
      echo $"Package file doesn't exist: " "$p" >&2
      return 1 
      fi
      if [[ "$ONLY_REAL_FILES" == "yes" && ! -f "$p" ]]; then
      continue
      fi
      if [[ "$DIFF_TO_BASE" ]]; then
          older_copy="$DIFF_TO_BASE/$type/${p##*/}" # using shell param expansion instead of `basename' to speed up
          if [[ -h "$older_copy" || -a "$older_copy" ]]; then
          continue
      fi
      fi
      allFiles[$(( no++ ))]="$p"
  done
  readonly -a allFiles

  # Splitting the list of all files into future disks:
  # 
  local -a filesToEat allSizes
  let 'no = 0' ||:
  filesToEat=()
  allSizes=($(getSize "${allFiles[@]}"))
  readonly -a allSizes
  # allSizes contains the sizes corrsponding to allFiles
  # filesToEat hold the constructed list of files to put on the current disk
  # no points to the next free position in filesToEat
  # totalSize should hold the sum of the sizes 
  #  of the files already put into filesToEat;
  #  it is set and reset externally.
  for p in "${allFiles[@]}"; do 
      if (( totalsize + ${allSizes[$(( no ))]} > CDVOLUME )); then
      eatFiles "${filesToEat[@]}"
          filesToEat=()
          finishCD
      startTypedCD
    fi
      let "totalsize += ${allSizes[$(( no ))]}" ||:
      filesToEat[$(( no++ ))]="$p"
  done
  eatFiles "${filesToEat[@]}"
}

function eatFiles() {
    #{ oldIFS="$IFS"; IFS=$'\n'; echo "$FUNCNAME: args: " "$*" | head >&2;  IFS="$oldIFS"; }
    zeroDelimited "$@" | xargs -0 --no-run-if-empty \
    cp -s \
    --target-dir="$THIS_LAYOUTS_DIR/cd$(( cdN ))/$PREFIX/$type$DOT_SUFFIX"/ \
    --
}

function startTypedCD() {
#  set -x
  mkdir -p "$THIS_LAYOUTS_DIR/cd$(( cdN ))/$PREFIX/$type$DOT_SUFFIX"
  start_action $" %s with %s" "$(( cdN ))" "$type"
#  set +x
}

function finishCD() {

阅读第 454 行后的更多内容

请注意,该eatFiles函数将未来磁盘的布局准备为树,其中叶子是真实文件的符号链接。因此,您应该能够在刻录之前编辑布局,这符合您的要求。这mkisofs实用程序有一个跟踪符号链接的选项,这确实在代码中使用我的mkiso职能

所提供的脚本(当然,您可以根据您的需要进行重写!)遵循最简单的想法:按照文件distribute列出的顺序对文件(或者更准确地说,在 的情况下是包)的大小进行求和,不要不做任何重新安排。

“Haskell 漫游指南”更认真地对待优化问题,并建议尝试巧妙地重新排列文件的程序变体,以便它们更好地适合磁盘(并且需要更少的磁盘):

前期准备已经够多了。我们去收拾一些CD吧。

正如您可能已经认识到的那样,我们的问题是一个经典问题。它被称为“背包问题”谷歌搜索一下,如果您还不知道它是什么。有超过100000个链接)。

让我们从贪心解开始......

(阅读更多内容第3章并进一步。)

其他智能工具

我还被告知 Debian 使用一个工具来制作它的发行版 CD,该工具比我的distributewrt 软件包集合更智能:它的结果更好,因为它关心软件包之间的依赖关系,并且会尝试制作可运行的软件包集合。第一个磁盘在依赖项下关闭,即,第一个磁盘中的任何包都不应需要另一个磁盘中的包(或者至少,我想说,应最大限度地减少此类依赖项的数量)。

相关内容