How to find files between two dates using "find"?

I have an email account that has passed 60GB of emails, and currently I'm having a lot of trouble using an email client to archive emails of last year (2011).

Via terminal, I am trying to use find to locate the files between 2011-01-01 and 2011-12-31, but no avail.

How can I find files between two dates?

If relevant, the end goal will be a batch that will move each file found, matching the date interval, to a folder.


Bash find files between two dates:

find . -type f -newermt 2010-10-07 ! -newermt 2014-10-08

Returns a list of files that have timestamps after 2010-10-07 and before 2014-10-08

Bash find files from 15 minutes ago until now:

find . -type f -mmin -15

Returns a list of files that have timestamps after 15 minutes ago but before now.

Bash find files between two timestamps:

find . -type f -newermt "2014-10-08 10:17:00" ! -newermt "2014-10-08 10:53:00"

Returns files with timestamps between 2014-10-08 10:17:00 and 2014-10-08 10:53:00


You can use this script:

#!/bin/bash
for i in $(find Your_Mail_Dir/ -newermt "2011-01-01" ! -newermt "2011-12-31"); do
  mv $i /moved_emails_dir/
done

Moving the files, and prompting the user when there are duplicate names:

As Subv3rsion's and Eric Leschinski's answers show, the -newermt predicate selects files modified more recently than the date (and optional time) specified as its operand. To find files

  • anywhere in srcdir (i.e., including its subdirectories, their subdirectories, etc.)
  • last modified in (for example) September 2014
  • and move them to destdir

...you can run:

find srcdir -type f -newermt 2014-08-31 ! -newermt 2014-09-30 -exec mv -i {} destdir/ \;

In an -exec expression, find passes the filename found in place of {}. ; signifies to -exec that the command to be run, and its arguments, have all been provided (in case subsequent expressions are passed to find after that particular -exec predicate's arguments--see below for an example of this). ; must be escaped as \; so it's not interpreted specially by the shell. (Without \, ; would end the whole find command, working the same as a newline. Even though this find command has nothing after this -exec expression, failing to pass the ; argument is still a syntax error.)

If you just want to list the files--which is advisable if you're not sure how the old emails are stored or what other files may be present--omit -exec and everything to the right of it. (For email, often emails from different dates are stored in the same file; for someone in the situation described in the question here, I recommend investigating how they are stored before moving any files.) If you want to both print their names and move them, add -print before -exec.

mv -i prompts anytime a file would be overwritten at the destination, such as would happen if:

  • a file of the same name exists from a previous backup, or
  • a file of the same name but from a different subdirectory of srcdir has already been moved during the same find operation, or
  • (least likely) a file of the same name was created somewhere in srcdir during the same find operation, after the original was moved but soon enough to be found once find traverses a different subdirectory.

Other ways to invoke mv:

You have other options for how to handle files with duplicate names.

  • Without -i (i.e., mv {} destdir/), mv would not usually prompt for approval, but would do so if the destination file were read-only. (mv can even succeed at overwriting a read-only file sometimes, such as if the user running it owns the file.)
  • If you don't want even that degree of interactivity, and want mv always to (attempt to) overwrite identically named files, use mv -f.
  • If, in contrast, you want to skip source files when there is already a destination file of the same name, use mv -n.
  • mv accepts the -b and --backup flags to automatically rename identically named files that already exist at the destination. By default ~ is added to produce the backup name, and if a file with the name and a file with the backup name already exist at the destination, the backup file is overwritten. This default can be overridden by options passed when invoking mv, and by environment variables. See man mv for details, and the example below.

Moving the files and creating backups in case of duplicate names:

To move all files, back up files with duplicate names using a ~ suffix, and use numbered .~n~ suffixes when .~ files already exist (so as to avoid overwriting anything), run:

find srcdir -type f -newermt 2014-08-31 ! -newermt 2014-09-30 -exec mv --backup=existing {} destdir/ \;

If you skipped files with duplicate names and want to know which ones:

If you use mv -n and want to know which files were not moved because there was another file with the same name, the best way is probably just to run the original find command again, without -exec and everything to the right of it. This will print their names.
It will also print the names of any matching files created since you ran the original find .... -exec ... command, but for this application there will typically be none since you're looking for files with old modification times. It's possible to give a file a modification timestamp older than its real age, with touch and other mechanisms, but that doesn't seem likely to occur in this case without your knowledge.

Knowing immediately as files are skipped due to duplicate names:

mv -n doesn't report, nor return any special exit code, when it refrains from moving a file. So if you want to be immediately informed of skipped files while find runs, you'll have to make a separate step for that. One way is:

find srcdir -type f -newermt 2014-08-31 ! -newermt 2014-09-30 -exec mv -n {} destdir/ \; \
    -exec [ -f {} ] \; -exec printf "\`%s' skipped (exists in \`%s')\\n" {} destdir \; 

A few probably minor technical considerations: This warns incorrectly if mv fails to copy a file for a different reason than it existing at the destination and exits reporting success. That seems unlikely, but I'm not certain it's impossible. It also potentially suffers a race condition: it would warn when there is no real error at all, if a new file of the same name were created in the same place during the very short time after the old file was moved and before the check to see if it was removed. (Considering the application, I doubt either problem would ever actually occur.) It could be rewritten to check the destination before moving the file instead of after: then the race condition would relate to newly created destination files instead of source files. And while errors and warnings reported by find or mv (or [, though there shouldn't be any) will be written to standard error, our ...skipped (exists in... warning gets written to standard output. Normally both appear on your terminal, but this may matter if you're scripting.

I've split that command onto two lines for easier reading. It can be run that way, or you can remove the \ and the newline (i.e., the line break).

How does that find command work?

find predicates can be tests (like -type and -newermt), used for their return values, or actions (like -print and -exec), which are often used for their side effects.

When no operator (like -a for and, -o for or) is supplied between expressions, -a is implied. find employs short-circuit evaluation for and and or. p q (i.e., p -a q) is true only if the p and q expressions are both true, so q needn't be evaluated if p is false. Though we often don't think of it in these terms, this is why tests have to be true for subsequent actions or tests to be evaluated. For example, suppose find comes upon a directory. It evaluates -type f to false, so it can skip everything afterwards.

Like tests, actions evaluate to true or false as well. In this way, -exec reports if the executed command exited reporting success (true) or failure (false). We have this chain of -exec expressions connected with implicit and:

-exec mv -n {} destdir/ \; -exec [ -f {} ] \; -exec printf "\`%s' skipped (exists in \`%s')\\n" {} destdir \;

This tries to move the file, and if mv reports failure, stops. We don't want to warn about a correctly skipped file if some other problem was why it wasn't moved.

But if it succeeded, it then runs the [ command. Like find, [ supports its own kind of expressions passed as arguments. [ -f {} ] checks if the operand after -f (passed to it by find in place of {}) exists (and is a regular file), and returns either true/success or false/failure.
(Many commands' exit statuses are best interpreted as signifying success or failure, but ['s exist status is usually best interpreted as true or false.)

If [ returned false, then the file is gone, so it was moved, so there's no need to do anything. But if [ returned false, the file is still there. Then find evaluates the next -exec expression, which prints the warning message.

Further Reading

  • man find and the GNU Findutils reference manual
  • man mv and the GNU Coreutils reference manual (especially 11.4 mv: Move (rename) files)
  • man \[ and 16.3 test: Check file types and compare values in the Coreutils docs
    This (i.e., /usr/bin/[), and not the shell's [ builtin, is what find runs when you use -exec [.