How to sort files into directories and subdirectories by name?

I'm new to ubuntu and also to the programming world, and I don't really know how to do this!

I have a folder with lots of files named as follows:

000_S_001_mpc_asd.json
000_S_001_mpc_asd.nii
000_S_001_mpc_asd_aa.nii
011_S_001_mpc_asd.json
011_S_001_mpc_asd.nii
011_S_001_mpc_asd_aa.nii
000_S_002_mpc_asd.json
000_S_002_mpc_asd.nii
000_S_002_mpc_asd_aa.nii
000_S_001_dtd_rty.bval
000_S_001_dtd_rty.bvec
000_S_001_dtd_rty.nii
000_S_001_dtd_rty.json
011_S_001_dtd_rty.bval
011_S_001_dtd_rty.bvec
011_S_001_dtd_rty.nii
011_S_001_dtd_rty.json
000_S_002_dtd_rty.bval
000_S_002_dtd_rty.bvec
000_S_002_dtd_rty.nii
000_S_002_dtd_rty.json
011_S_001_flf_lkj.json
011_S_001_flf_lkj.nii
011_S_001_flf_lkj_aa.nii
000_S_001_flf_lkj.json
000_S_001_flf_lkj.nii
000_S_001_flf_lkj_aa.nii
000_S_002_flf_lkj.nii
000_S_002_flf_lkj_aa.nii

Let's say xxx_S_xxx is the principal name, and the rest of the file's name gives secondary information (let's call it a secondary name).

I would like to find a specific name into the secondary name and make a folder with this name (for example mpc, dtd or flf), then make subfolders named as the principal name of each file, and into those folders put the respective files. Probably an image will explain better what I'm trying to say.

So for example, the output for the names I gave you above would look like this:

Desired output:

Is this possible to do from the terminal? I would appreciate your help.

My OS is Ubuntu 20.04 LTS


Solution 1:

In cases where the target directory structure does not exist, you can chop the path into pieces and resemble them in the desired order.

#!/bin/bash

while IFS=_ read -r a b c d e; do
    mkdir -p target/$d/${a##*/}_${b}_${c}; mv -t $_ ${a}_${b}_${c}_${d}_$e
done < <(printf %s\\n files/*)

Or a regular expression, where the array BASH_REMATCH records the matched parts. The first array member contains the part that matches the whole regular expression. Substrings matched by parenthesized subexpressions are assigned to the following members.

#!/bin/bash

for i in files/*; do
    if [[ $i =~ ^files/([0-9]+_S_[0-9]+)_(mpc|dtd|flf) ]]; then
        mkdir -p target/${BASH_REMATCH[2]}/${BASH_REMATCH[1]}; mv -t $_ $i
    fi
done

You can also divide the process into two steps where you start by creating the directory structure first, awk in combination with xargs optimizes the use of mkdir and then use, e.g., mmv for the renaming.

#!/bin/bash

builtin cd files

printf %s\\0 * | \
awk -F _ ' \
    BEGIN{ RS = ORS = "\0" } { printf("../target/%s/%s_%s_%s\0", $4, $1, $2, $3) } \
' | xargs -0 mkdir -p

# Remember we have change our working directory.
mmv -m '*_S_*_*_*' '../target/#3/#1_S_#2/#1_S_#2_#3_#4'