I have a directory containing about 7k folders. These folders were extracted from zips and some of the extraction was done using Python scripts. Some of these folders are extracted in such a way that

             Main Dir
        |               |
      fold1            fold2
        |               |
      ------         -------
    |        |          |
  .pngs   .txts       fold2
                    |       |
                  .pngs    .txts

The requirement is to move the fold2 category of folders in a directory structure similar to that of fold1 where a folder contains the data instead of another same named folder containing the data.

How may I do it using bash or command line so that I have all 7k folders in a homogeneous structure similar to fold1?


The following script will search the current working directory for paths of the form a/B/B/c and compress them to a/B/c. This also compresses a/B/B/B/B/c to a/B/c and a/B/B/c/D/D/e to a/B/c/D/e.

You need GNU find to use the -regextype option and an implementation of mv supporting -n. If you don't have these please have a look at the unsafe posix version at the end of the script.

shopt -s dotglob failglob
find . -depth -regextype egrep -type d -regex '.*/([^/]*)/\1' -print0 |
while IFS= read -r -d '' path; do
  mv -n -t "$path/.." "$path"/* &&
  rmdir "$path"

Arbitrary path names (whitespaces, special symbols like *, and even linebreaks) are supported.

The command makes sure not to overwrite or delete any files. In a situation as in the left tree the repeated sub-directory has to be kept. You will get the error message rmdir: failed to remove './A/A'. The result can be seen to the right.

. (before)               . (after)
└── A                    └── A
    ├── someFile             ├── someFile
    ├── collision            ├── collision
    └── A                    ├── anotherFile
        ├── collision        └── A
        └── anotherFile          └── collision

Hidden files are copied too.

Bad Posix Version

A more portable version of the script which cannot handle line breaks inside paths, may overwrite files in situations as the one shown above, and cannot move hidden files (sub-directory is kept if there are hidden files inside).

find . -depth -type d | grep -E -x '.*/([^/]*)/\1' |
while IFS= read -r path; do
  mv "$path"/* "$path/.." &&
  rmdir "$path"
| improve this answer | |

Not sure if it's what you need but...

mv /maindir/fold2/ /maindir/renamed/
mv /maindir/renamed/fold2/ /maindir/fold2/

then, renamed should be empty, delete it.

or this should do the job too

mv maindir/fold2/fold2/{*,.*} maindir/fold2/

then /maindir/fold2/fold2/ should be empty

| improve this answer | |
  • 1
    I don't think this is what OP wanted. You only showed how to move one folder manually but OP has 7000 of them. Also, the last mv command will give two error messages since .* also matches . and ... – Socowi May 10 '19 at 15:49
  • Well, there is not that much details in his question, that's why I posted what I could understand in it. And about the "error" message, it's fine. – darxmurf May 10 '19 at 17:37
  • On arch linux and ubuntu I get two warnings from the last command (as expected). Don't you get errors too for the following test case? mkdir a a/a a/a/b; mv a/a/{*,.*} a/. – Socowi May 10 '19 at 17:41

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.