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 samefind
operation, or -
(least likely) a file of the same name was created somewhere in
srcdir
during the samefind
operation, after the original was moved but soon enough to be found oncefind
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, usemv -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 invokingmv
, and by environment variables. Seeman 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.4mv
: 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 whatfind
runs when you use-exec [
.