4

Is there a command line one-liner to move all the contents of all the direct subfolders of a folder to the folder itself and deleting the subfolders (which are now empty)? I.e., eliminate a level in the folder hierarchy?

To be absolutely clear, the deeper subfolder structure should be preserved.

I've run into this a couple of times now, and I was a little disappointed this doesn't seem to be a feature of Dolphin, nor something other people are struggling with.

I suspect this may have to be split in two, moving the contents of the subfolders, and deleting the subfolders. However, this may have the undesired side-effect of deleting empty second-level sub-folders.

oulenz
  • 232

3 Answers3

5

Actually not a real one-liner:

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

First find everything in the subfolder with exact depth 2 and move it to .. Then find and delete all empty folders in the current directory.

  • if you have files/folders with the same name they will be renamed with original_filename.~n~ (n meaning 1, 2, 3 ...).
  • Note that will delete all empty subfolders.

Update:

I'm not happy with the solution above. Too many caveats and problems. For example when --backup=numbered renames the parent folder of your folder you want to move ...

mkdir -p test/test
mv test/test . --backup=numbered
mv: cannot move 'test/test' to './test': No such file or directory

Better use a temporary directory to avoid problems. The temp dir should be on the same file system to avoid the need to copy the files.

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
pLumo
  • 27,991
1

To guard against deleting empty subdirectories, IMO the simplest way is to move to a temporary directory and then rename the temporary directory to the old one. Say the directory in question is foo, and you want foo/*/* to become foo/*. Do something like:

tar c --remove-files foo | tar xv --strip-components=2 --backup=t --one-top-level=temp
mv temp foo
  • With --remove-files, the first tar will delete the files as it processes them, finally deleting foo itself.
  • With --strip-components=2, the second tar will chop off the foo/*/ from paths it is extracting, so foo/a/b will become b.
  • --backup=t is the same as with cp or mv - make numbered backup copies. However, mv makes copies of directories if they have the same name, but tar will merge the directories and make backups of files.
  • --one-top-level=temp tells tar to create a directory named temp and extract files there.

For example:

$ 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

And after running the tar commands:

$ 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

With the tar output, you can see when tar renamed files for backing up, allowing you track which files became which backups.


I'm mildly surprised that it works, but you can actually provide the same directory name for --one-top-level and eliminate need to rename. Just this pipeline is enough:

tar c --remove-files bar | tar xv --strip-components=2 --backup=t --one-top-level=bar
muru
  • 207,228
-1
mv */* . && rmdir *

Move everything in subdirectories into the current directories. rmdir won't delete directories unless they're empty so the second part only deletes empty directories.