1

I have a filesystem that I would like to organise alphabetically. It's large and to do it manually would take a lifetime.

What I would like to know is whether it would be possible to write something in the terminal that will create folders A-Z and then have the terminal search the entire filesystem and move all subfolders into their respective letters folder.

I would also like the search to organise based upon the file extension found inside the subfolder.

for example find and move all subfolders beginning with the letter A containing file extension .doc to folderA.

Thanks in advance

4
  • Am I understanding you correctly that you want only the direct first-level subdirectories of your working directory sorted into alphabetical folders? I am not sure what you mean with "recursive" here.
    – Byte Commander
    Feb 6, 2017 at 18:54
  • Yes that is right
    – Johnny
    Feb 6, 2017 at 19:22
  • So where is the recursiveness then? Do you want to sort all subdirectories of each original directory into alphabetical folders? So would ./apples/bananas/cherries become ./A/apples/bananas/cherries or ./A/apples/B/bananas/C/cherries? And what about files - sort them too, or only directories?
    – Byte Commander
    Feb 6, 2017 at 19:26
  • by recursive I mean that it will go through the whole working folder. the second example would be best and just the directories not the files inside
    – Johnny
    Feb 6, 2017 at 19:38

1 Answer 1

2

You can use the find command below to recursively (depth-first) move all folders into a folder named after the first letter of their file name, creating it if it does not exist yet, starting from your current working directory:

find . -depth -mindepth 1 -type d -execdir bash -c 'l=./$(basename "{}" | head -c1) ; test -d "${l^^}" || mkdir "${l^^}" ; mv "{}" "${l^^}/"' \;

Explanation:

  • find . -depth -mindepth 1 -type d -execdir <COMMAND> \;
    recursively searches your current directory (.), depth-first (-depth), for all directories (-type d), excluding your current directory itself (-mindepth 1).
    Then it cds into each found folder's parent directory and executes <COMMAND> from there, replacing every occurrence of {} with the relative found directory name (-execdir).
  • bash -c '<COMMAND>'
    starts a Bash subshell and lets it interpret and execute the string <COMMAND>. We need this because we're going to use shell features like variables, pipes, command substitution, etc, which can't be done directly by find -execdir.
  • l=./$(basename "{}" | head -c1)
    stores the first letter of the found directory name (remember that {} gets substituted with the search result) in the shell variable $l.
  • test -d "${l^^}" || mkdir "${l^^}"
    checks whether a directory exists and creates it if not. The name of the directory ("${l^^}") is the content of the variable $l (the first letter of the found directory name) converted to upper case.

  • mv "{}" "${l^^}/"
    moves the found directory (remember again that {} gets substituted) into the directory named after its first letter converted to upper case (`"${l^^}/", which we prepared earlier).

Example run:

$ tree -F
.
├── apple/
│   ├── hamster/
│   ├── horse/
│   ├── snake/
│   │   ├── blue
│   │   ├── green
│   │   ├── grey
│   │   └── red
│   └── spider/
├── apricot/
├── banana/
├── cherry/
│   ├── pink
│   ├── purple
│   └── yellow
└── coconut/

9 directories, 7 files
$ find . -depth -mindepth 1 -type d -execdir bash -c 'l=./$(basename "{}" | head -c1) ; test -d "${l^^}" || mkdir "${l^^}" ; mv "{}" "${l^^}/"' \;
$ tree -F
.
├── A/
│   ├── apple/
│   │   ├── H/
│   │   │   ├── hamster/
│   │   │   └── horse/
│   │   └── S/
│   │       ├── snake/
│   │       │   ├── blue
│   │       │   ├── green
│   │       │   ├── grey
│   │       │   └── red
│   │       └── spider/
│   └── apricot/
├── B/
│   └── banana/
└── C/
    ├── cherry/
    │   ├── pink
    │   ├── purple
    │   └── yellow
    └── coconut/

14 directories, 7 files

Edit:

If you want it non-recursively, i.e. operating only on the direct first-level subdirectories of your current working directory, simply add the -maxdepth 1 option to the find command. You can omit the -depth option then though:

find . -mindepth 1 -maxdepth 1 -type d -execdir bash -c 'l=./$(basename "{}" | head -c1) ; test -d "${l^^}" || mkdir "${l^^}" ; mv "{}" "${l^^}/"' \;

Edit 2:

It is possible to only perform the move if the directory to move contains files matching a specific criterion (here: contain a file with file name ending in .jpg anywhere, including subdirectories), but it adds another find command and an if clause:

find . -depth -mindepth 1 -maxdepth 1 -type d -execdir bash -c 'if find "{}" -type f -iname '*.jpg' | grep -q '.' ; then l=./$(basename "{}" | head -c1) ; test -d "${l^^}" || mkdir "${l^^}" ; mv "{}" "${l^^}/" ; fi' \;

Explanation (after edit 2):

  • if <CONDITION> ; then <COMMANDS> ; fi
    Checks the exit code of the command <CONDITION> and only runs the <COMMANDS> between then and fi if the condition was fulfilled (exit code 0). <COMMANDS> is the same as in the previous version.
  • find "{}" -type f -iname '*.jpg' | grep -q '.' Searches inside the directory found by the outer find command (remember once again that {} gets substituted) for files (-type f) with a name that ends with .jpg, case-insensitively (-iname '*.jpg').
    The grep -q '.' simply checks whether the find produces any output on STDOUT (means that files were found) and returns an exit code of 0 then, 1 if no files were found.

Example run (after edit 2):

$ tree -F
.
├── apple/
│   ├── hamster/
│   ├── horse/
│   ├── snake/
│   │   ├── blue
│   │   ├── green
│   │   ├── grey
│   │   └── red
│   └── spider/
├── apricot/
├── banana/
│   └── black.jpg
├── cherry/
│   ├── pink
│   ├── purple
│   └── yellow
└── coconut/

9 directories, 8 files

$ find . -depth -mindepth 1 -maxdepth 1 -type d -execdir bash -c 'if find "{}" -type f -iname '*.jpg' | grep -q '.' ; then l=./$(basename "{}" | head -c1) ; test -d "${l^^}" || mkdir "${l^^}" ; mv "{}" "${l^^}/" ; fi' \;
$ tree -F
.
├── A/
│   └── apple/
│       ├── hamster/
│       ├── horse/
│       │   └── white.jpg
│       ├── snake/
│       │   ├── blue
│       │   ├── green
│       │   ├── grey
│       │   └── red
│       └── spider/
├── apricot/
├── B/
│   └── banana/
│       └── black.jpg
├── cherry/
│   ├── pink
│   ├── purple
│   └── yellow
└── coconut/

11 directories, 9 files
13
  • Ok it looks good, except I think I've mislead you, I would actually prefer not to sort the subdirectories, just all of the primary directories in the file tree.
    – Johnny
    Feb 6, 2017 at 20:33
  • @Johnny Well, you said "recursive" and when I gave you the two apple/banana/cherry examples, you picked the second one. Anyway, to disable the recursiveness, just add -maxdepth 1 as argument to the find command, e.g. like this (shortened): find . -depth -mindepth 1 -maxdepth 1 -type d -execdir <COMMAND> \;
    – Byte Commander
    Feb 6, 2017 at 20:37
  • Also don't forget to accept this answer by clicking the grey round check button on its left if it fully solves your question. Otherwise feel free to ask back if something is still unclear or suboptimal. Thanks and welcome to Ask Ubuntu.
    – Byte Commander
    Feb 6, 2017 at 20:41
  • Ok I also want to tell the find command to only move folders with a certain filetype inside, so that it will only move the folders with .jpg files in for example
    – Johnny
    Feb 6, 2017 at 20:48
  • @Johnny It has been suggested in chat that you keep changing the specs of what you're asking for. Please state all the things you need the commands to do, so that we don't have to constantly revise scripts just to adjust to a new specification.
    – Thomas Ward
    Feb 6, 2017 at 20:54

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .