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
...
As the answer to the question you linked already states, mv
can suffix files that would otherwise get overwritten by the file you move with a number to give them a unique file name:
mv --backup=t <source_file> <dest_file>
The command works by appending the next unused number suffix to the file that was first in the destination directory. The file you are moving will keep its original name.
However, this will appends suffixes like .~1~
, which seems to be not what you want:
$ ls
file.pdf
file.pdf.~1~
file.pdf.~2~
You can rename those files in a second step though to get the names in a format like file_1.pdf
instead of file.pdf.~1~
, e.g. like this:
rename 's/((?:\..+)?)\.~(\d+)~$/_$2$1/' *.~*~
This takes all files that end with the unwanted backup suffix (by matching with the shell glob *.~*~
) and lets the rename
tool try to match the regular expression ((?:\..+)?)\.~(\d+)~$
on the file name. If this matches, it will capture the index from the .~1~
-like suffix as second group ($2
) and optionally, if the file name has an extension before that suffix like .pdf
, that will be captured by the first group ($1
). Then it replaces the complete matched file name part with _$2$1
, inserting the captured values instead of the placeholders though.
Basically it will rename e.g. file.pdf.~1~
to file_1.pdf
and something.~42~
to something_42
, but it can not detect whether a file has multiple extensions, so e.g. archive.tar.gz.~5~
would become archive.tar_5.gz
Best Answer
In Unix, almost everything is a file. A directory is a special type of file that from the user's perspective can "contain" other files.
The error
Not a directory
occurs because your existing file is not a directory, and since a directory is a type of file, and there cannot be two identically named files in one directory, the operation cannot be performed.