10

I have this kind of directory tree that is obtained from unzipping a zip file:

x -> y -> z -> run -> FILES AND DIRECTORIES HERE

So there are 4 directories, 3 of whom are empty of files (x, y, z) and only contain 1 sub-directory, and there is the directory I am interested in, named "run".

I want to move the "run" directory itself (including everything within) to the "root" location where I unzipped (i.e. where "x" is, but not inside "x").

Assumptions: There exists a folder named "run", but I do not know how many directories I will have to "cd" to get to it (could be 3 (x,y,z), could be 10 or more. The names are also unknown and do not have to be x,y,z etc).

How can I accomplish this? I tried many variations of this question but they all failed.

Rui F Ribeiro
  • 56,709
  • 26
  • 150
  • 232
Idos
  • 203

4 Answers4

14

what about

  find . -type d -name run -exec mv {} /path/to/X \;

where

  • /path/to/X is your destination directory
  • you start from this same place.
  • then use other answer to remove empty directories.

(on a side note there is a --junk-paths option in zip, either when zipping or unzipping)

Archemar
  • 31,554
7

I would do this in bash, using globstar. As explained in man bash:

globstar
      If set, the pattern ** used in a pathname expansion con‐
      text will match all files and zero or  more  directories
      and  subdirectories.  If the pattern is followed by a /,
      only directories and subdirectories match.

So, to move the directory run to the top level directory x, and then delete the rest, you can do:

shopt -s globstar; mv x/**/run/ x/  && find x/ -type d -empty -delete

The shopt command enables the globstar option. The mv x/**/run/ x/ will move any subdirectories named run (note that this only works if there is only one run directory) to x and the find will delete any empty directories.

You could do the whole thing in the shell with extended globbing, if you like, but I prefer the safety net of find -empty to be sure no non-empty directories are deleted. If you don't care about that, you can use:

shopt -s globstar; shopt -s extglob; mv x/**/run/ x/ && rm -rf x/!(run)
terdon
  • 242,166
6

Definitely more verbose, but doing the job in one step:

Assuming you have python installed

#!/usr/bin/env python3
import shutil
import os
import sys

dr = sys.argv[1]

for root, dirs, files in os.walk(dr):
    # find your folder "run"
    for pth in [d for d in dirs if d == sys.argv[2]]:
        # move the folder to the root directory
        shutil.move(os.path.join(root, pth), os.path.join(dr, pth))
        # remove the folder (top of the tree) from the directory
        shutil.rmtree(os.path.join(dr, root.replace(dr, "").split("/")[1]))

How to use

  1. Copy the script into an empty file, save it as get_folder.py
  2. Run it with the root directory (containg your unzipped stuff) and the name of the folder to "lift" as arguments:

    python3 /full/path/to/get_folder.py /full/path/to/folder_containing_unzipped_dir run
    

And it is done:

enter image description here

enter image description here

Jacob Vlijm
  • 349
  • 1
  • 8
  • This is a very interesting solution but I prefer not to use any python script for various reasons. Thanks tho! – Idos Mar 05 '17 at 11:37
  • No problem. Maybe it is of use to anyone else. Curious to why though? I am pretty sure many applications already use python. – Jacob Vlijm Mar 05 '17 at 11:38
  • Mainly since placing the .py file anywhere will be problematic as I have to assume it is in the same place for every linux machine that will be running my script (and my script is run across many platforms). – Idos Mar 05 '17 at 11:44
  • @Idos just curious, which platforms ? Python is quite widespread. I don't think it is installed on Mac OS X by default, but most Linux distributions do have it – Sergiy Kolodyazhnyy Mar 05 '17 at 23:35
4

Perl solution

#!/usr/bin/env perl
use strict;
use warnings;
use File::Find;
use File::Copy::Recursive qw(dirmove);

my @wanted;
find(sub{ -d $_ && $_ eq "run" && push @wanted,$File::Find::name}, $ARGV[0]);
dirmove("$wanted[0]","./run");

The way this works is simple: find() subroutine will traverse given directory tree recursively, find the directory whose name is run and push it into array of wanted filenames. In this case, we assume there should be only one file in the list, thus once command completes, we use dirmove() from File::Copy::Recursive module, and move it to current working directory from which script was called. Thus in your case you would call it from parent directory to x.

Original directory tree:

$ tree                                                                                                                   
.
└── levelone
    └── leveltwo
        └── levelthree
            └── run
                ├── file1
                ├── file2
                └── file3

Result:

$ ~/find_dir.pl .  
$ tree 
.
├── levelone
│   └── leveltwo
│       └── levelthree
└── run
    ├── file1
    ├── file2
    └── file3