Sort Files By Mime-type then Modified Date

What command sort’s text files into sub-directories based on date modified

I would like to follow the following command format.

find /path/to/source/* -type f -iname '*.txt' -print0 | xargs -0 mv -t /path/to/target

To help properly, we need a bit more detail:

  • How do you want the sub-directories structured (year / year-month / year-month-day)?
  • Should the directories be created automatically?
  • Are filename collisions a concern?

Also, note that mv does not care about sort order — so the task is really about grouping files by modification date, not sorting.

1 Like

I need help with this because I’ve never been able to figure the proper placements of the different symbols, options, arguments, varables, etc.

Also I would like to have it to where one script works on both my fiance and mine desktop.
Previously I commented in and out the individual paths.
That can get tedious.

structured as mime-type / year-month please.

Yes, please create automatically as I usually run from terminal without using a current working directory.

I used --backup=numbered previously and now it seemed the copied files are growing so much that my command needs to run without the --backup=numbered allowing the files to be narrowed to a minimum filing structure without deleting the image files jpg png bmp gif etc. I have duplicate image files with different dimensions that I need to keep.

Thanks for the clarification :+1:
Given your requirements:

  • Directory structure: mime-type / year-month
  • Directories created automatically
  • No --backup=numbered
  • Do NOT delete or overwrite files
  • Preserve duplicate images (same name, different dimensions)

Below is a safe and robust Bash solution that works correctly with spaces and special characters.

Example result

image/jpeg/2025-01/
text/plain/2024-11/

Command (recommended approach)

SOURCE="/path/to/source"
TARGET="/path/to/target"

find "$SOURCE" -type f -print0 |
while IFS= read -r -d '' file; do
    mime=$(file --mime-type -b "$file")
    date=$(date -r "$file" +%Y-%m)

    dest="$TARGET/$mime/$date"
    mkdir -p "$dest"

    mv -n "$file" "$dest/"
done

Why this works well

  • Uses MIME type instead of extensions (more reliable)
  • Groups files by year-month
  • mkdir -p ensures directories are created automatically
  • mv -n prevents overwriting existing files
    → duplicates are preserved, nothing is deleted
  • Handles spaces and special characters safely
  • Does not rely on sort order (which mv doesn’t care about anyway)
2 Likes

a couple more details…

I need help with this because I’ve never been able to figure the proper placements of the different symbols, options, arguments, varables, etc.

Also I would like to have it to where one script works on both my fiance and mine desktop.
Previously I commented in and out the individual paths.
That can get tedious.

That makes sense :+1: — and you’re not alone; shell syntax and quoting rules are one of the hardest parts to get comfortable with.

Here’s how to address both of your concerns:
(1) understanding where things go, and (2) using one script on multiple machines without editing it every time.


Key idea: make the script self-configuring

Instead of hard-coding paths, we let the script detect the current user and build paths automatically.
That way the same script works on your desktop and your fiancée’s desktop with no commenting/uncommenting.


Portable script (no hard-coded usernames)

#!/usr/bin/env bash

SOURCE="$HOME/path/to/source"
TARGET="$HOME/path/to/target"

find "$SOURCE" -type f -print0 |
while IFS= read -r -d '' file; do
    mime=$(file --mime-type -b "$file")
    date=$(date -r "$file" +%Y-%m)

    dest="$TARGET/$mime/$date"
    mkdir -p "$dest"

    mv -n "$file" "$dest/"
done

Why this works on both systems

  • $HOME automatically expands to:

    • /home/wyatt on your system
    • /home/fiancee on hers
  • No need to edit the script at all

  • Safe to run from any directory


Explaining the “symbols” (quick mental model)

Variables

SOURCE="$HOME/Documents"
  • Left side = variable name
  • Right side = value
  • Quotes protect spaces

Command substitution

mime=$(file --mime-type -b "$file")
  • $( ... ) means “run this command and store its output”

Why -print0 and -d '' are paired

find ... -print0 | while IFS= read -r -d '' file
  • This pair allows filenames with:

    • spaces
    • newlines
    • weird characters
  • Always used together


Why we don’t use xargs here

  • xargs becomes fragile once logic is needed
  • Loops are clearer and safer for complex file handling

Optional: make it even easier to reuse

If both of you keep files in the same relative location, you’re done.

If not, you can allow command-line arguments:

SOURCE="${1:-$HOME/source}"
TARGET="${2:-$HOME/sorted}"

Then run:

./sort-files.sh ~/Downloads ~/Sorted
2 Likes

She needs her comp as a coping helps mechanism while I am the overall general maintainer of both systems and other living need’s, including involved internet as a utility as opposed to a luxury.

Thanks to all for the detail’d guidance and help here.

I’ll get this together and see what it throws at us.
Hopefully it all goes well.

That’s completely understandable :+1:
Using a single, portable script with no hard-coded paths is exactly the right approach when you’re maintaining multiple systems and need to keep one of them stable and stress-free.

Take your time testing it on a small set of files first, and adjust as needed.

Also, once you confirm which reply solved your issue, it would be helpful to mark that post as the solution — it really helps future users who run into the same problem.

1 Like

Looking at the details as an executable file…

Where do I put it and what permissions do I set?
I usually set the permissions as “chmod a+x /location/of/user/level/executable/file.sh”
(Usually in a custom folder in home)
I know there’s a better location to keep the exec files such as this to allow them to be maintained automatically.

Where to put the script

For a user-level executable, the usual and recommended place is:

~/.local/bin/

Most modern distributions already have this directory in $PATH.
If it doesn’t exist yet, create it:

mkdir -p ~/.local/bin

Then move the script there, for example:

mv sort-files.sh ~/.local/bin/

Permissions

You only need to make it executable by the user:

chmod u+x ~/.local/bin/sort-files.sh

Using a+x works too, but u+x is cleaner and safer for user scripts.


Running it

Once it’s in ~/.local/bin and executable, you can run it from anywhere:

sort-files.sh
1 Like

so I don’t need to specify the absolute path to the file in order for it to work when that file is located in ~/.local/bin?

Doing that, will it be auto-maintained when some of the details within the script needs to be updated?

Do I need the absolute path?

No.
If the script is in ~/.local/bin and that directory is in your $PATH, you can run it by name only:

sort-files.sh

You can confirm with:

echo "$PATH" | tr ':' '\n' | grep .local/bin

If it shows up, no absolute path is needed.


Will it be “auto-maintained”?

Yes — the file itself is the single source of truth.

  • When you edit:

    ~/.local/bin/sort-files.sh
    
  • The next time you run:

    sort-files.sh
    

    you’re automatically using the updated version.

There’s no caching, no reinstall, no relogin required.


One small best practice

Use a stable name and just edit the file in place:

nano ~/.local/bin/sort-files.sh

As long as:

  • the filename stays the same
  • it remains executable

…every invocation will use the latest changes.

1 Like

Thanks so much.
I’ve been awake for almost 56 hours straight thanks to my landlord’s employer invoking military standards on civilian living. (yes, I am a US military veteran)

I need sleep and will continue after the well-needed rest.

You’re very welcome. Please get some rest — after being awake that long, that’s the right priority.
Nothing here is urgent, and you can continue once you’re well-rested.

How does this look so far…
Is it ok to create a symlink on the Desktop?
Does it look like it might throw some warnings, errors, etc?
(I plan to run it in a “testing set up” before setting it to the main system)
Where I have the “Set Variables”, is that location in the script ok or does that need to be where you originally put them?

#!/usr/bin/env bash

### Specify file name for pattern matching
## Guidance from - @TheFu @ian-weisser
# sort-files.sh

## Note: Spaces at the beginning of commands to reduce recording in .bashrc
## and/or .bash_history
## Guidance from - @guiverc

###================================================================###

### Script Location
## For a **user-level executable**
## the usual and recommended place is
# $   ~/.local/bin/

## Most modern distributions already have this directory in `$PATH`.
## If it doesn’t exist yet, create it:
# $   mkdir -p ~/.local/bin

## Then move the script there, for example:
# $   mv sort-files.sh ~/.local/bin/

###================================================================###

### Set Permissions
## User level scripts need to be executable by the user
## Using `a+x` works too, but `u+x` is cleaner and safer for user scripts
# $   chmod u+x ~/.local/bin/sort-files.sh

### Run Script From Anywhere
# $   sort-files.sh

###================================================================###

### Set Variables

# Source Variables
SOURCE="$HOME/path/to/source"

# Target Variables
TARGET="$HOME/path/to/target"

###================================================================###

### The Script - Portable (no hard-coded usernames)

   find "$SOURCE" -type f -print0 |
   while IFS= read -r -d '' file; do
       mime=$(file --mime-type -b "$file")
       date=$(date -r "$file" +%Y-%m)

       dest="$TARGET/$mime/$date"
       mkdir -p "$dest"

       mv -n "$file" "$dest/"
   done

###================================================================###

######################################################################

:one: Is it OK to create a symlink on the Desktop?

Yes, absolutely.
That’s actually a good usability choice.

Example:

ln -s ~/.local/bin/sort-files.sh ~/Desktop/sort-files
  • The script still lives in the correct place
  • The Desktop link is just a convenience
  • No warnings or side effects from this

:two: Will this throw warnings or errors?

Nothing obvious :+1:
As written, it should run cleanly assuming:

  • SOURCE exists
  • TARGET exists (or is creatable)
  • file command is installed (it usually is)

Optional (not required):
You could add a quick sanity check later, but for testing this is fine.


:three: Is the “Set Variables” location OK?

Yes — this is exactly where they should be.

Best practice is:

  • Variables near the top
  • Logic below them

You did that correctly. There is no requirement that they be in a specific absolute position, only that they’re defined before use.


:four: Minor optional notes (not required)

  • The leading spaces before find are harmless (shell ignores them)
  • Your comments are verbose, but that’s a good thing for future maintenance
  • The script is portable and safe for both systems

You’re doing this the right way. Test it on a small dataset, then promote it to the main system when ready.

I think it is very important to clarify whether both machines are the same in terms of the flavour and version installed (as well as the filesystem type).

Running a test on a small batch would definitely be advisable.

Final point: does the script remove the empty source folders and is this something you want to happen?

Her’s and mine both are running Lubuntu 24.04.

Absolutely
I usually set up a temporary directory on the desktop with sub-directories.
However, using this bash setup is helping me require better ways of doing things.

Thats a good point.
There’s a command in my original executable that deletes the empty directory(s) in the source directory
It may be more safe to use “rmdir” instead of “find”.
(That might depend on your individual use case as to which command you might need)
One of the 2 commands removes only empty directories.

$   find "$SOURCE"/* -type d -empty -delete
1 Like

Terminal threw a couple complaints…

wyatt@wyatt-82xb:~$ mkdir -p ~/.local/bin wyatt@wyatt-82xb:~$ mv sort-files.sh ~/.local/bin/ mv: cannot stat 'sort-files.sh': No such file or directory
For this instance, I used my old command

wyatt@wyatt-82xb:~$    mv /home/wyatt/Desktop/sort-files.sh ~/.local/bin/
wyatt@wyatt-82xb:~$    chmod u+x ~/.local/bin/sort-files.sh

The chmod u+x performed as expected.

And finally…the following…

wyatt@wyatt-82xb:~$    sort-files.sh
find: ‘/home/wyatt/path/to/source’: No such file or directory
wyatt@wyatt-82xb:~$ 

Those messages are expected and don’t indicate a problem with the script :+1:

  • The mv: cannot stat error happened because the file wasn’t in the current directory. Using the full path from Desktop was the correct fix.

  • The find: No such file or directory message is because

    SOURCE="$HOME/path/to/source"
    

    is just a placeholder and needs to be replaced with a real, existing path.

Once SOURCE and TARGET are set to valid directories, the script should run without errors.