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
  • 2
  • 9
  • 1
    how would you like to handle the case when two sub-folders in level2 have the same name? – Yaron May 08 '17 at 08:52
  • Yes, it's in two parts, move them, and then delete the (now empty) subdirectories: `rmdir folder/*/` `rmdir` will complain about non-empty directories, so no harm will be done by it. – muru May 08 '17 at 08:52
  • @Yaron However move conflicts are normally handled on the command line. Ideally when this occurs the user should get prompted whether they want to skip the sub-sub-folder (and thus also the relevant sub-folder), whether they want to overwrite the existing folder or whether they want to write into it. – oulenz May 08 '17 at 09:00
  • 1
    @muru this is not a duplicate ! The pseudo duplicate answer moves all subfiles (whatever the depth) in the current folder. This not what OP asked, OP needs to preserve directory structure. – exore May 08 '17 at 09:24
  • @RoVo Thanks! If you turn that into an answer I can accept it. Two things though: 1) a problem I didn't think of, see edit 2) why do you move files and folders separately? – oulenz May 08 '17 at 09:32
  • I thought it may cause problems, but it won't. See my answer :-) – pLumo May 08 '17 at 09:33

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
  • 26,204
  • 2
  • 57
  • 87
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
  • 193,181
  • 53
  • 473
  • 722
-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.