Bash Script to Sort Files into Alphabetical Folders on ReadyNAS Duo v1

Here's a bit of a one liner that will do what you want:

$ mkdir -p output/{A..Z}; for i in tstdir/*; do export FILE=$(basename "$i");  LTR=$(echo" ${FILE:0:1}" | tr [a-z] [A-Z]); mv "$i" "output/$LTR/$FILE" ; done

Here's the same command in expanded form so you can see what's going on:

$ mkdir -p output/{A..Z}
$ for i in tstdir/*; do 
    FILE=$(basename "$i")  
    LTR=$(echo "${FILE:0:1}" | tr [a-z] [A-Z])
    mv "$i" "output/$LTR/$FILE"
  done

Details

The above first assumes that the output directory of just the letters doesn't exist and so will create it,

$ mkdir -p output/{A..Z}

The for loop works as follows, looping through all the files in tstdir/*. It then determines the basename of this path, and stores it in the variable, $FILE. Each iteration through the loop is stored in the variable $i.

FILE=$(basename "$i")

We then use Bashes ability to return the 1st character of the named variable, $FILE, and then use tr to convert any lowercase characters to uppers.

LTR=$(echo "${FILE:0:1}" | tr [a-z] [A-Z])

Breaking this down a bit more:

$ echo "${FILE:0:1}"
s
$ echo "${FILE:0:1}"
T

With the tr code you can now see what's going on:

$ echo "${FILE:0:1}" | tr [a-z] [A-Z]
S
$ echo "${FILE:0:1}" | tr [a-z] [A-Z]
T

The rest of the command just moves the files into their corresponding first letter directory.

Example

Say we have this directory of files:

$ touch {a-z}file {A-Z}file

$ tree tstdir/ | head -10
tstdir/
|-- afile
|-- Afile
|-- bfile
|-- Bfile
|-- cfile
|-- Cfile
|-- dfile
|-- Dfile
|-- efile
...

After running the one liner:

$ tree output/ | head -10
output/
|-- A
|   |-- afile
|   `-- Afile
|-- B
|   |-- bfile
|   `-- Bfile
|-- C
|   |-- cfile
|   `-- Cfile
...

With zsh

mkmv() {mkdir -p -- $argv[-1]:h && mv "$@"}
autoload zmv
zmodload zsh/files

zmv -Qp mkmv '(?)*(^-/)' '${(U)1}/$f'
  • zmv a function to safely batch-rename files using zsh powerful pattern matching and expansion operators as an autoloadable function.
  • mkmv: a function that acts like mv except that it also creates the parent directory of the target if need be.
  • $argv[-1]: $argv like $* is the list of positional parameters, $argv[-1] is the last one. Here, same as $3 as zmv calls it as mkmv -- source destination
  • $var:h: like in csh, gets the head of the variable, that is the dir name.
  • zmodload zsh/files: loads a module that enables the builtin version of a few file handling utilities including mkdir and mv, here giving a significative performance enhancement as we're calling both for each file.
  • -Q: enable bare glob qualifiers in the pattern. Theses days, another option is to rewrite (^-/) as (#q^-/).
  • -p mkmv, tell zmv to use our mkmv function as the program to do the renaming instead of mv
  • (?)*(^-/): a pattern (?)* with a glob qualifier used to match the files to rename. The ? (to match a single character) in parenthesis is captured so it can be referred to as $1 in the replacement.
  • (^-/): glob qualifiers used to match files based on more criteria than just their name:
    • ^: negate the following qualifiers
    • -: for the following qualifiers, for symlinks, consider the attribute of the target of the link instead of the link itself.
    • /: select files of type directory. With the previous two qualifiers, that means we want files that are neither directories nor symlinks to directories.
  • ${(U)1}: the captured first character of the matched file, converted to uppercase with the U parameter expansion flag
  • $f in the replacement refers to the full path of the matched file.

Try with this script:

for first in $(ls -1 | sed 's/^\(.\).*$/\1/' | tr '[a-z0-9]' '[A-Z0-9]' | uniq)
do
    mkdir tmp
    mv "$first"* tmp/
    lower=$(echo $first | tr '[A-Z]' '[a-z]')
    mv "$lower"* tmp/
    mv tmp/ "$first";
done

The logic is as follows:

  1. List all the files in the current directory.
  2. Extract the first character with sed.
  3. Change the first character to lower case with tr, this is to reduce the number of comparisons.
  4. Remove duplicates with uniq.
  5. Create a temporary directory and move all the files that start with the character to this folder.
  6. Rename the temporary file.

I used these files to test the script:

Apple.txt
avocado.txt
banana.txt
broccoli.txt
car.txt
dddd.txt
delete.txt
zaad.txt
zdfa.txt

Here is the result:

.
├── A
│   ├── Apple.txt
│   └── avocado.txt
├── B
│   ├── banana.txt
│   └── broccoli.txt
├── C
│   └── car.txt
├── D
│   ├── dddd.txt
│   └── delete.txt
└── Z
    ├── zaad.txt
    └── zdfa.txt

5 directories, 10 files