Script to Organize Desktop into Folders/Directories by Extension
Solution 1
If what you want is a directory per file type, then you want something more like:
mkdir txt || { echo "Can't mkdir txt" ; exit 1 ; }
for file in *.txt ; do
mv -- "$file" txt/
done
Note the failure handling on mkdir
. In your code, if there is already a file with the name contained in the variable $dir
, your mkdir --"$dir"
command will silently fail, and then your mv
command will overwrite that file.
To handle a bunch of arbitrarily named files—all the files with three-character extensions in the current directory—and put them in directories by file extension, you could use the following code (untested):
for file in *.??? ; do
[ -f "$file" ] || continue
dir="$(echo "$file" | rev | cut -c-3 | rev)"
mkdir -p "$dir" || { echo "Couldn't mkdir -p $dir; exiting" ; exit 1 ; }
mv -- "$file" "$dir"
done
Explanation:
The pattern *.???
will match all files and directories in the current directory whose name ends in a period (.
) followed by exactly three characters.
The test [ -f "$file" ]
returns true only if $file
is a regular file (not a directory or other more specialized type like a device.) Otherwise, the continue
statement will skip the rest of that pass through the for
loop.
The assignment to the dir
variable looks fancy but really it's just taking the contents of the $file
variable, reversing it, extracting the first 3 characters (of the reversed string), then reversing that again, and assigning the result as the value of $dir
. In other words it's the last three characters of the filename.
Then, mkdir -p
will return success if the directory already exists, or will create it if it doesn't (and return success). If it doesn't return success, you don't want to try to move files into it! Hence the error handling.
Then, the mv
command just like normal.
A note about error handling:
You might see constructs like this:
mkdir -p "$dir" || echo "Failed, exiting" && exit 1
This is imperfect because the exit
command is only executed if the echo
command succeeds after the mkdir
command fails. What you want instead is to have the exit
command executed regardless of whether the echo
command succeeds, assuming that mkdir
failed. This is why I use the curly braces as above.
Solution 2
If what you want is a directory per file type, then you want something more like:
for file in *.*[!.]
do
ext=${file##*.}
mkdir -p -- "$ext" && mv -- "$file" "$ext"/
done
Unlike other operating systems, in Unix, *.*
means, not all files,
but all files whose names contain at least one .
(dot, a.k.a. period),
thus omitting files with names like txt
, todolist
and oddball
.
(To confuse matters, shell wildcards exclude files
whose names begin with a .
unless the dotglob
option is set.)
*.*[!.]
(or, equivalently, *.*[^.]
) further restricts this
to files whose names contain at least one .
but do not have .
as the last character,
thus omitting files with names like oddball.
and odd.ball.
.
AFAICT, this is a good characterization
of files whose names have a non-null extension.
ext=${file##*.}
extracts that extension,
by deleting everything up to and including the last dot.
If the above analysis is correct, this should be non-null.
The -p
option causes mkdir
not to fail if the argument already exists
(and is a directory).
If you have plain files with names like txt
, pdf
, and jpg
,
and also files with names like shopping_list.txt
(which will yield ext=txt
), or in some other edge cases
(e.g., you don’t have write permission in the current directory,
or there’s no space left in the filesystem),
the mkdir
will fail and will issue its own diagnostic message.
AND, because of the &&
(as suggested by @Wildcard),
the following mv
will not be attempted.
The /
at the end of the mv
command is a safety net to prevent the clobbering of plain files with names like txt
, pdf
, and jpg
.
mv -- "$file" "$ext"/
will simply fail if the destination name exists as a plain file (or any file type other than a directory).
Related videos on Youtube
Comments
-
systemfive over 1 year
I'd like to organize desktops by running a script that places all of your files into folders by extension. I found some code through googling and the code I found creates a folder with the name of the file and places the file in said folder. I don't want a folder per file name... I want one folder to have all .txt, another for .jpg... etc. I use OS X.
Here's what I have... any direction would be great, thanks!
for file in *.txt do dir="${file%.txt}" mkdir -- "$dir" mv -- "$file" "$dir" done
-
gardenhead over 8 yearsWow thanks for the tip on error handling! That's something I hadn't thought about.
-
Wildcard over 8 yearsThe assignment of
ext
here is simple to look at, but will fail (badly) on some edge cases. For instance, a filename that ends in a.
will causeext
to be set to a null string, and bothmkdir
andmv
will fail silently. (Withmv -- "$file" "$ext"/
it will fail unless you have permission to write to/
, which you hopefully don't when you run this script.) (cont'd) -
Wildcard over 8 yearsAlso consider odd "extensions" such as a filename
birthday party.todolist
. If you also have a file calledtodolist
, it would be silently overwritten by yourbirthday party.todolist
. Further, if you have a whole bunch offiles.todolist
, all of them will disappear except for the last one, which will be renamed astodolist
. Always test for error conditions. -
G-Man Says 'Reinstate Monica' over 8 yearsOK, good point about filenames ending with
.
, but what do you mean “mv
will fail silently”? A command likemv oddfile. ""
fails with “mv: cannot move ‘oddfile.’ to ‘’: No such file or directory”. And I’ve already acknowledged that a plain file calledtxt
causes problems. What doestodolist
add to the discussion? -
G-Man Says 'Reinstate Monica' over 8 yearsNot everything that needs to be done, needs to be done well. Absolutely, if you’re writing a polished gem of a script to install on your system for future use, and to distribute to other people, it should include error checking. This is the sort of thing that might be a one-off situation. In such a situation, it might be good enough to run the script interactively and look for the error message(s) from
mkdir
andmv
. -
systemfive over 8 yearsQuestion: Does this script only group extensions with 3 letters? "???" What about extensions with "????" like .html?
-
Wildcard over 8 yearsSorry, rather than "fail silently" I should have said "fail without preventing bad stuff". I do take your meaning about one-off situations—my comments are the sort of info re robustness that I loved to find on this site when I was first learning
bash
scripting. (Pay it forward.... ;) -
Wildcard over 8 years@JovanHernandez, yes, I designed it to only handle 3-letter extensions, just because I like my code to be fairly explicit. You could adjust it to handle 4-letter extensions only (by adding a
?
to the glob pattern and fixing thecut -c-3
command) or handle both 3 and 4 letters by running two separatefor
loops in sequence. If you want to handle all extensions of any length, use G-Man's answer. (See my further comments below his answer.)