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 likemv
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
aszmv
calls it asmkmv -- source destination
$var:h
: like incsh
, 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 includingmkdir
andmv
, 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
, tellzmv
to use ourmkmv
function as the program to do the renaming instead ofmv
(?)*(^-/)
: 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 theU
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:
- List all the files in the current directory.
- Extract the first character with
sed
. - Change the first character to lower case with
tr
, this is to reduce the number of comparisons. - Remove duplicates with
uniq
. - Create a temporary directory and move all the files that start with the character to this folder.
- 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