我需要一种方法来搜索目录中具有相同名称的子目录,然后将子目录中的所有文件移动到父目录。因此,
/recup-dir1/recup-dir1/files to /recup-dir1/files.
子目录可以留空,因为我可以使用类似
find . -type -d -empty -delete
删除所有空目录的方法
所以问题是我不知道哪些目录中有同名的子目录,哪些目录没有。
在伪代码中我需要这样的东西。
While more directories are unchecked
get name-x of next dir
enter dir
If name-x/name-x exist
move all files in name-x/name-x to name-x
mark dir as done
next
我最好的猜测是创建一个小 python 脚本来创建一个包含同名子目录的所有目录的列表,并通过如下命令循环此列表
find something something -exec mv
也许这可以通过 bash 脚本来完成,或者存在其他解决方案。就像一些 rsync 命令一样,但是由于我可能使用 rsync 创建了这个混乱,我认为这不是解决方案。
编辑:这是树输出的实际部分:顶层目录位于 /mnt/external-disk/tst-backup 内,较低级别没有子目录。
│ └── recup_dir.1
├── recup_dir.10
│ └── recup_dir.10
├── recup_dir.100
│ └── recup_dir.100
├── recup_dir.102
│ └── recup_dir.102
└── recup_dir.1020
└── recup_dir.1020
答案1
有了zsh
,你可以这样做:
for dir in **/*(NDodoN/e['[[ $REPLY:t = $REPLY:h:t ]]']); do
contents=($dir/*(NDoN))
(( $#contents == 0 )) ||
mv -- $contents $dir:h/ &&
rmdir -- $dir
done
在哪里:
**/*(qualifiers)
使用 glob 限定符进行递归 globbingN
: nullglob: 如果没有匹配,不要抱怨D
: dotglob: 包含隐藏文件od
:顺序深度优先(叶子在它们所在的分支之前)。oN
:否则不必费心排序文件列表。/
:限制为目录类型的文件。e['expression']
:限制expression
代码返回 true 的文件(当前文件路径存储在其中$REPLY
)。$REPLY:t
: 文件的尾部(基本名称)$REPLY:h:t
:文件头(目录名)的尾部)
对于bash
4.4+ 和 GNUfind
或find
大多数 BSD,您可以执行类似的操作:
shopt -s nullglob dotglob
readarray -td '' dirs < <(
LC_ALL=C find . -depth -regex '.*\(/[^/]*\)\1' -type d -print0
)
for d in "${dirs[@]}"; do
contents=("$d"/*)
(( ${#contents[@]} == 0 )) ||
mv -- "${contents[@]}" "${d%/*}/" &&
rmdir -- "$d"
done
这次使用正则表达式来匹配./path/to/dir/dir
使用基本正则表达式反向引用的文件。
答案2
尝试一下,基于 GNU find
v4.8.0 和 Bash v5.1.8
第 1 部分:解析目录树 + 检测子目录名称重复
假设树中的某个目录具有以下结构:
./
|__test1/
|__dirname with space
| |__test2
| |__ test2
|__dirname **
| |__test1
|
|__reboot
| |__test1
|
|__test2/
|__test3/
|__test2/
|__test1/
|__test1/
(奇怪的目录名称是为了证明代码安全性。)
您会看到一些子目录(subdirs)以不同的方式重复。有些重复多次,而不仅仅是一次(例如test1
),一个不重复(test3
),并且它们可以作为父目录和子目录重复,也可以由任意数量的中间子目录分隔。
下面的代码详细地揭示了目录结构中的子目录名称欺骗。
- 它解析文件树以获取从以下位置开始的子目录结构
$PWD
- 它会查找 2 个或更多级别的任何子目录路径的每个组件的重复项,不计算根级别( )
$PWD
。在我的实验中,最长的子目录路径是:./test1/test2/test1/test3/test2/test1/test1
- 它打印在每个子目录级别找到的第一个子目录副本,从叶子开始,即从右到左读取子目录路径。
- 打印以相反的顺序重定向到文件,因此首先显示最长的子目录路径。两个连续的分号将路径组件(“;;”左侧)与根据上一个项目符号找到的第一个重复项(“;;”右侧)分隔开。
[代码]
$ find ./* -type d -exec bash -c 'set -o noglob; IFS="/" subdir=($(printf "%s " "$1")); dirlevels=$((${#subdir[@]}-1)); dupe="$(awk '\''!($1 in sd) {sd[$1];next} {print $1}'\'' < <(printf "%s\n" ${subdir[@]:1}))";[ $dirlevels -ge 2 ] && [ ! -z "$dupe" ] && (printf "%s/" "${subdir[@]:1}";printf " ;; %s\n" "$(tail -n 1 < <(printf "%s\n" "$dupe"))";)' shellexec {} \; | tac >| tmp.data
$ cat -n tmp.data
1 test1/reboot/test1/ ;; test1
2 test1/dirname with space/test2/test2/ ;; test2
3 test1/test2/test1/test3/test2/test1/test1/ ;; test1
4 test1/test2/test1/test3/test2/test1/ ;; test1
5 test1/test2/test1/test3/test2/ ;; test2
6 test1/test2/test1/test3/ ;; test1
7 test1/test2/test1/ ;; test1
8 test1/dirname **/test1/ ;; test1
第 2 部分:子目录名称重复的处理;移动内容
处理按照 中显示的顺序进行tmp.data
。
- 在
tmp.data
的第一行,路径上第一个被欺骗的名字./test1/test2/test1/test3/test2/test1/test1
是test1
。我们可以将其内容传输到最左边的同名子目录:./test1/
- 一旦内容被移动且没有破坏目标处的现有文件,最右边的子目录级别
test1
将被删除。 - 我们继续执行第 2 行
tmp.data
并重复上述步骤。 - 等等,直到所有行都
tmp.data
被消耗掉。
在这个阶段,问题(问题的作者:@TomDerks)是如何处理test1/*
第 6 行最右边的内容?应该全部其内容是否被移动到最左边的同名目录,在本例中是路径上的第一个子目录级别? “全部”是否包含以下文件./test1/test2/test1/
也子目录test3
及其内容?
完整的解决方案(第 2 部分)取决于此。