![如何移动一组文件和目录,排除另一组(子)文件和目录](https://linux22.com/image/208231/%E5%A6%82%E4%BD%95%E7%A7%BB%E5%8A%A8%E4%B8%80%E7%BB%84%E6%96%87%E4%BB%B6%E5%92%8C%E7%9B%AE%E5%BD%95%EF%BC%8C%E6%8E%92%E9%99%A4%E5%8F%A6%E4%B8%80%E7%BB%84%EF%BC%88%E5%AD%90%EF%BC%89%E6%96%87%E4%BB%B6%E5%92%8C%E7%9B%AE%E5%BD%95.png)
我有两个 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
-al
zsh
bash
cpio
-0
--null
rm
-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()
需要,因此相对路径此后将不再引用同一文件)。$dst
cd
$src
destination
$^array/x
对数组进行类似rc
/ 的扩展,例如,如果包含and作为元素,则它会变成而不是。fish
$array
A
B
Ax
Bx
A
Bx
A{x,y}
是类似 csh 的大括号扩展,同样扩展为Ax
,Ay
。**/
匹配任何级别的子目录。- 在 中
glob~pattern
,是此处用于应用排除的~
and-not / except运算符。extendedglob
(${(~j[|])exclude})(|/*)
j
是通过添加数组元素(由于参数扩展标志|
而被视为全局运算符而不是文字)构造的排除模式,我们附加到该排除模式以匹配其中的元素或任何文件。|
~
(|/*)
(ND)
glob 限定符将N
ullglob 和D
otglob 应用于这些 glob,以便包含隐藏文件,并且如果 glob 不匹配,则不会生成错误。print -rNC1
在列上打印其参数r
aw1
C
,以N
UL 分隔。${(Oa)array}
以反向a
排列O
顺序扩展,因此叶子在它们所在的树枝之前被移除。zargs
有没有可以避免的参数列表太长如果要删除的文件列表太大,则会出现错误。