如何解散所有子文件夹

有没有一条命令行指令可以将文件夹的所有直接子文件夹中的内容移动到该文件夹本身,并删除这些子文件夹(现在为空)?也就是说,消除文件夹层次结构中的一个级别?
为了绝对清楚,更深层次的子文件夹结构应该被保留。
我遇到过几次这个问题,有点失望的是 Dolphin 似乎没有这个功能,其他人也没有遇到类似的问题。
我怀疑这可能需要分成两步来完成,先移动子文件夹的内容,然后再删除子文件夹。然而,这可能会导致删除空的二级子文件夹,这是不希望发生的副作用。

1当第二级目录中的两个子文件夹具有相同的名称时,您希望如何处理该情况? - Yaron
是的,它分为两个部分,先移动它们,然后删除(现在为空的)子目录:rmdir folder/*/ rmdir 会报错非空目录,所以不会造成任何损害。 - muru
@Yaron 但是移动冲突通常在命令行上处理。理想情况下,当出现这种情况时,用户应该被提示是否要跳过子子文件夹(以及相关的子文件夹),是否要覆盖现有文件夹,或者是否要写入其中。 - oulenz
1@muru 这不是一个重复的问题!伪重复答案会将当前文件夹中的所有子文件(无论深度如何)移动。这不是OP所问的,OP需要保留目录结构。 - exore
@RoVo 谢谢!如果你把那个变成一个答案,我就可以接受了。不过有两件事情:1)有一个问题我没有考虑到,请看编辑2)为什么你要分别移动文件和文件夹呢? - oulenz
我以为这可能会引起问题,但实际上不会。请看我的回答 :-) - pLumo
3个回答

实际上并不是一句真正的一行话:

find . -mindepth 2 -maxdepth 2 -print -exec mv --backup=numbered -t . '{}' + \
&& find . -mindepth 1 -maxdepth 1 -type d -empty -delete

首先,在子文件夹中找到所有深度为2的内容,并将其移动到当前目录下。然后,查找并删除当前目录中的所有空文件夹。
  • 如果您有相同名称的文件/文件夹,它们将被重命名为original_filename.~n~(其中n表示1、2、3...)。
  • 请注意,这将删除所有空的子文件夹。

更新:
我对上面的解决方案不满意。有太多的限制和问题。例如,当--backup=numbered重命名您要移动的文件夹的父文件夹时...
mkdir -p test/test
mv test/test . --backup=numbered
mv: cannot move 'test/test' to './test': No such file or directory

最好使用临时目录来避免问题。临时目录应该位于同一文件系统上,以避免需要复制文件的需求。
tmpdir=$(mktemp -d)
find . -mindepth 2 -maxdepth 2 -print -exec mv -b -t $tmpdir '{}' +
find . -mindepth 1 -maxdepth 1 -type d -empty -delete
mv -b $tmpdir/* .
rm $tmpdir

有一个小问题是,这也会删除空的二级子文件夹。 - oulenz
你对~n~是什么意思? - oulenz
不,它只会删除“.”中的空文件夹。请参考-mindepth/-maxdepth。 - pLumo
1--exec mv --backup=numbered -t . {} + 这样 find 命令会批量处理 mv 的调用。 - muru
@RoVo 是的,但是一旦那些空的二级子文件夹被移动到“。”,它们不会被第二个语句删除所有空的子文件夹吗? - oulenz

为了防止删除空的子目录,我个人认为最简单的方法是先移动到一个临时目录,然后将临时目录重命名为旧目录。假设要处理的目录是foo,你想让foo/*/*变成foo/*。可以这样操作:
tar c --remove-files foo | tar xv --strip-components=2 --backup=t --one-top-level=temp
mv temp foo
  • 使用--remove-files选项,第一个tar命令在处理文件时会删除它们,最终删除foo本身。
  • 使用--strip-components=2选项,第二个tar命令会从要提取的路径中去掉foo/*/,因此foo/a/b将变成b
  • --backup=tcpmv的作用相同-创建带有编号的备份副本。然而,如果目录具有相同的名称,mv会复制目录,但tar会合并目录并备份文件。
  • --one-top-level=temp告诉tar创建一个名为temp的目录,并在其中提取文件。

例如:

$ tree bar
bar
├── a
│   ├── c
│   │   └── bar
│   ├── d
│   │   └── bar
│   ├── e
│   │   └── bar
│   └── f
│       └── bar
├── b
│   ├── c
│   │   └── bar
│   ├── d
│   │   └── bar
│   ├── e
│   │   └── bar
│   └── f
│       └── bar
└── c
    ├── c
    │   └── bar
    ├── d
    │   └── bar
    ├── e
    │   └── bar
    └── f
        └── bar

在运行了tar命令之后:
$ tar c --remove-files bar | tar xv --strip-components=2 --backup=t --one-top-level=temp
bar/a/e/
bar/a/e/bar
bar/a/f/
bar/a/f/bar
bar/a/c/
bar/a/c/bar
bar/a/d/
bar/a/d/bar
bar/b/e/
bar/b/e/bar
Renaming ‘temp/e/bar’ to ‘temp/e/bar.~1~’
bar/b/f/
bar/b/f/bar
Renaming ‘temp/f/bar’ to ‘temp/f/bar.~1~’
bar/b/c/
bar/b/c/bar
Renaming ‘temp/c/bar’ to ‘temp/c/bar.~1~’
bar/b/d/
bar/b/d/bar
Renaming ‘temp/d/bar’ to ‘temp/d/bar.~1~’
bar/c/e/
bar/c/e/bar
Renaming ‘temp/e/bar’ to ‘temp/e/bar.~2~’
bar/c/f/
bar/c/f/bar
Renaming ‘temp/f/bar’ to ‘temp/f/bar.~2~’
bar/c/c/
bar/c/c/bar
Renaming ‘temp/c/bar’ to ‘temp/c/bar.~2~’
bar/c/d/
bar/c/d/bar
Renaming ‘temp/d/bar’ to ‘temp/d/bar.~2~’

$ tree temp
temp
├── c
│   ├── bar
│   ├── bar.~1~
│   └── bar.~2~
├── d
│   ├── bar
│   ├── bar.~1~
│   └── bar.~2~
├── e
│   ├── bar
│   ├── bar.~1~
│   └── bar.~2~
└── f
    ├── bar
    ├── bar.~1~
    └── bar.~2~

4 directories, 12 files

通过tar的输出,您可以看到tar在备份时重命名的文件,从而可以追踪哪些文件成为了哪些备份。
我有点惊讶它能够发挥作用,但实际上你可以为 --one-top-level 提供相同的目录名称,并消除重命名的需要。只需这个流程就足够了:
tar c --remove-files bar | tar xv --strip-components=2 --backup=t --one-top-level=bar

mv */* . && rmdir *

将所有子目录中的内容移动到当前目录中。rmdir命令只能删除空目录,因此第二部分只会删除空目录。