如何移动一组文件和目录,排除另一组(子)文件和目录

如何移动一组文件和目录,排除另一组(子)文件和目录

我有两个 bash 数组,其中一个(名为toMove)包含文件和目录的路径移动(不是复制)其他地方,另一个(名为exclude)包含要排除移动的文件和目录的路径。

toMove=(
    tree/subtree1
    tree/subtree2
    tree/subtree3/leaffile3
)
exclude=(
    tree/subtree1/leafdir1
    tree/subtree2/leaffile2
)

# move code ?

这是测试目录结构:

mkdir -p tree/{subtree1/leafdir1,subtree2/leafdir2,subtree3/leafdir3}
touch tree/{subtree1/leaffile1,subtree2/leaffile2,subtree3/leaffile3}
tree tree
    tree
    ├── subtree1
    │   ├── leafdir1
    │   └── leaffile1
    ├── subtree2
    │   ├── leafdir2
    │   └── leaffile2
    └── subtree3
        ├── leafdir3
        └── leaffile3

搬家后期望的结果:

tree tree
    tree
    ├── subtree1
    │   └── leafdir1
    ├── subtree2
    │   └── leaffile2
    └── subtree3
        └── leafdir3

tree destination
    destination
    ├── subtree1
    │   └── leaffile1
    ├── subtree2
    │   └── leafdir2
    └── subtree3
        └── leaffile3

在 中rsync,有一个--exclude-from=选项(与该选项结合--remove-source-files)完全符合我的要求,但不幸的是rsync 复制文件,但我需要移动他们(出于性能原因),如果它们位于同一文件系统上。

我想到的解决方案是使用 find 获取 toMove 数组中所有路径(包括目录的内容)的列表,迭代此列表并过滤掉以排除数组中的路径开头的所有路径。这是解决问题的正确方法,还是有更简单和/或更优雅的方法(可能使用一些标准实用程序)来解决这个问题?

更新:

事实证明,这个问题并不像人们第一次看到它时想象的那么微不足道,而且这个问题的措辞很糟糕——它应该是关于“从其他路径中过滤掉路径,保护整个不受影响的树”。

我最终采用了下面的解决方案,尽管它不保留空的 leafdirs,并且有第二个缺点,即它会移动每个文件,当它足够时,在某些情况下仅移动父树。

...
comm \
    -23 \
    <(find "${toMove[@]}"  ! -type d 2>/dev/null | sort -u || true) \
    <(find "${exclude[@]}" ! -type d 2>/dev/null | sort -u || true) |
        parallel -j "$(nproc)" -- moveFile {}
...

(moveFile 是一个导出的 bash 函数,用于处理移动。当脚本无法在一个文件系统上运行时,使用parallel 来加速(几次)。)

斯特凡·查泽拉斯的回答zsh告诉了我很多我以前不知道的事情,如果有选择的话,这似乎是可行的方法。

答案1

如果所有源和目标都在同一文件系统上,则移动是 a ,本质上与+rename()相同。link()unlink()

标准(虽然不广泛)pax命令、(以前的标准)命令和withcpio的 GNU 实现可以将目录结构复制为符号链接(不适用于仍需要重新创建的目录),因此 with代替和 with 的 GNU 实现(对于它的/ )和(对于它的/选项),你可以这样做:cp-alzshbashcpio-0--nullrm-d--dir

#! /bin/zsh -
src=tree
dst=destination
toMove=(
  subtree1
  subtree2
  subtree3/leaffile3
)
exclude=(
  subtree1/leafdir1
  subtree2/leaffile2
)

set -o extendedglob
autoload zargs

src=$src:P
dst=$dst:P

cd -- $src || exit

allToMove=( $^toMove{,/**/*}~(${(~j[|])exclude})(|/*)(ND) )
print -rNC1 -- $allToMove |
  cpio --pass-through --null --link --make-directories -- $dst &&
  zargs -r -- ${(Oa)allToMove} -- rm -d --

我们最终重新创建所有目标目录(并链接其中的所有文件),甚至那些我们可以刚刚重命名的目录,因此仍然可以改进,但至少没有复制任何非目录文件的数据。

--make-directories由于未包含在要移动的列表中(如示例中的目录)而创建的目录subtree3将不会从源复制其元数据(所有权、权限等)。

您将看到一些有关无法删除目录的警告,因为它们不为空。

其中使用的一些 zsh 功能包括:

  • $var:P扩展为存储的文件的绝对规范路径,$var就像在其上使用标准一样(我们进入时realpath()需要,因此相对路径此后将不再引用同一文件)。$dstcd$srcdestination
  • $^array/x对数组进行类似rc/ 的扩展,例如,如果包含and作为元素,则它会变成而不是。fish$arrayABAx BxA Bx
  • A{x,y}是类似 csh 的大括号扩展,同样扩展为Ax, Ay
  • **/匹配任何级别的子目录。
  • 在 中glob~pattern,是此处用于应用排除的~and-not / except运算符。extendedglob
  • (${(~j[|])exclude})(|/*)j是通过添加数组元素(由于参数扩展标志|而被视为全局运算符而不是文字)构造的排除模式,我们附加到该排除模式以匹配其中的元素或任何文件。|~(|/*)
  • (ND)glob 限定符将Nullglob 和Dotglob 应用于这些 glob,以便包含隐藏文件,并且如果 glob 不匹配,则不会生成错误。
  • print -rNC1在列上打印其参数raw 1 C,以NUL 分隔。
  • ${(Oa)array}以反向a排列O顺序扩展,因此叶子在它们所在的树枝之前被移除。
  • zargs有没有可以避免的参数列表太长如果要删除的文件列表太大,则会出现错误。

相关内容