Batch Modifying Modification Dates |
orlando_furioso 03-15-2006, 08:44 AM So on the plus side I have a nice new computer. On the bad side all my project files as copied off my old machine have some very unlikely modification and creation dates associated with them (I'm not sure what I'll be doing in 2040, but I sincerely hope it isn't working on files from 34 years ago). And there are a LOT of them. Too many to go in and touch them all individually. To compound the problem, my billing system relies on these dates.
I think I've figured out what the offset is, so I'm looking for some help in constructing a script to take the weird creation/modification dates and subtract, say 34 years, 4 months, and 12 days. Just to make life interesting, there are several files that I've worked on in the intervening months which have correct dates. So I'm guessing I can just focus on any files with dates, say 10 years ahead of the present and ignore the others.
I'm new to UNIX commands, but through seaching, have discovered that the "touch" command with the "-t" switch seems to be the one I want to assign the value, but I'm sort of lost about how to do the rest. Also if anyone has any ideas about how to accomplish this with AppleScript I'm all ears. Any thanks at all would be greatly appreciated.
This is a script I'd love to stick in the hints section of the main site (unless it's there already and I didn't find it).
hayne 03-15-2006, 09:08 AM You could use the 'find' command to find all files whose dates are in the future.
E.g. suppose you decide to look at the modification date.
That is the date that is shown by 'ls -l'
First create a dummy file that will be used as a date marker:
touch foo
The following 'find' command will show all files under the current folder (.)whose modification dates are after that of 'foo':
find . -type f -newer foo
If you use "-exec" with 'find', you can run a command on each file it finds that matches the specified criteria.
E.g.:
find . -type f -newer foo -exec mycommand {} \;
would run 'mycommand' (which could be a script that you wrote) on each of the "newer" files.
You should of course test these commands on a copy of your project folder to make sure they are doing what you want.
It would also be interesting to understand why the dates got modified when you copied the files over from your old machine. I would try to understand this before proceeding to try to fix the dates. Understanding the issue might lead to a better fix.
And you might find this Unix FAQ (http://forums.macosxhints.com/showthread.php?t=40648) useful.
orlando_furioso 03-15-2006, 07:42 PM Thank you, Cameron. That -newer seems very useful for sorting out which files need fixin'.
I'm not exactly sure what went wrong with the dates (I can't help but think of John Rhys Davies as Indiana Jones's Egyptian compatriot saying "Bad dates" and pointing to the dead monkey). The nearest I can figure is the old machine was left unplugged for a while before I transferred, and so maybe the clocks were off. Also I did the transfer through my hinky router so that may have contributed. Honestly, I wish that recopying was an option as I know how to do that. This stuff? Less so.
So I've been fiddling a bit, and my big stumbling block ATM is how to extract just the date. Is there a special function to output the modification (or creation) date of a specific file or do I need to figure out how to take apart the output of "ls-l"? Any suggestions?
This isn't my first experience with coding per se, just UNIX scripting. Which is to say that I've got a test directory all set up so I'm ready to break some stuff.
Here's some dummy code:#!/bin/sh
# When finished this script will hopefully alter the modification dates
# and creation dates for all files in a directory by subtracting
# an offset called dateoffset Please note this is DUMMY CODE
# and won't actually work.
cutoffdate = today + (10 * years)
dateoffset = (34 * years) + (4 * months) + (12 * days)
for file in *; do
# will fix the modification date of each file
# please note this is TOTALLY dummy code
thismoddate = getmoddate ($file)
if [ thismoddate > cutoffdate];
then
touch $file -t (thismoddate + dateoffset)
fi
# will fix the creation date
# please note this is TOTALLY dummy code
thiscreatdate = getcreatdate ($file)
if [ thiscreatdate > cutoffdate];
then
touch $file -t (thiscreatdate + dateoffset)
fi
done
I think ideally it'd be neat if you could pass the cutoffdate and dateoffset to the command, but I understand it's a very strict structure with the days minutes and seconds.
hayne 03-15-2006, 08:44 PM I think it would be easiest to get the date info by using the 'stat' command.
Read 'man stat' for details.
Here's an example that shows how you could get the info from 'stat' on a file "foo" into "st_*" variables that can be used in your shell script:
eval $(stat -s foo)
echo "modification time: $st_mtime"
To see why the above works, just run the 'stat -s' command on a sample file (without the 'eval')
The time is in seconds since the epoch (http://en.wikipedia.org/wiki/Unix_epoch) (January 1, 1970).
It would be easiest for you to always work in epoch seconds. Just work out what the offset you want is in seconds.
hayne 03-15-2006, 08:49 PM To pass parameters to a shell script, just type them on the command line with spaces between. Inside the script, the parameters are $1, $2, etc.
See the last section of this Unix FAQ (http://forums.macosxhints.com/showthread.php?t=40648) for references to Bash tutorials.
orlando_furioso 03-16-2006, 01:24 AM Wow, Cameron. Thanks again. Sorry to be such a newb. I feel like I'm starting to get it now though. Took me a LONG time to sort out how to do math.
I think I'll hold off on passing the parameters until I can get this working for this specific instance.
Interestingly, the stat -s yielded a negative modification date. I'm not sure what that means other than something went seriously wrong. Right now I'm at:#!/bin/sh
# This Script will hopefully correct the modification dates
# and creation dates for all files in a directory by subtracting
# an offset called dateoffset
# The following function's purpose is to convert Epoch dates/times
# to Gregorian dates and times specifically in the format:
# [[CC]YY]MMDDhhmm[.SS]
# This function does not take into account Leap years or the like.
# Actually it doesn't really work at all.
epochToGregor () {
local year=$((1970 + ($1 / 31536000)))
local days=$((($1 % 31536000) / 86400))
if [ $days -le 31 ]; then # January
month="01"
elif [ $days -le 59 ]; then # February
month="02"
days=$(($days-31))
elif [ $days -le 90 ]; then # March
month="03"
days=$(($days-59))
elif [ $days -le 120 ]; then # April
month="04"
days=$(($days-90))
elif [ $days -le 151 ]; then # May
month="05"
days=$(($days-120))
elif [ $days -le 181 ]; then # June
month="06"
days=$(($days-151))
elif [ $days -le 212 ]; then # July
month="07"
days=$(($days-181))
elif [ $days -le 243 ]; then # August
month="08"
days=$(($days-212))
elif [ $days -le 273 ]; then # September
month="09"
days=$(($days-243))
elif [ $days -le 304 ]; then # October
month="10"
days=$(($days-273))
elif [ $days -le 334 ]; then # November
month="11"
days=$(($days-304))
else # December
month="12"
days=$(($days-334))
fi
local hours=$((($1 % 84600) / 3600))
if [ $hours -lt 10 ]; then
hours="0$hours"
fi
local minutes=$((($1 % 3600) / 60))
if [ $minutes -lt 10 ]; then
minutes="0$minutes"
fi
local seconds=$(($1 % 60))
if [ $seconds -lt 10 ]; then
seconds="0$seconds"
fi
echo "$year""$month""$days""$hours""$minutes.$seconds"
}
# Creates/modifies a temp file: foo and figures out when rightnow is
# based on its modification date
touch foo
eval $(stat -s foo)
rightnow=$st_mtime
echo "Right now in Epoch Time it's: $rightnow"
# for safety, only files created, say 30 years ago or earlier
# will be affected 946,080,000 seconds is 30 years of seconds
cutoffdate=$(($rightnow - 946080000))
echo "******* cutoffdate = $cutoffdate"
# using the date information from when I emailed the file
# and correcting for the date as shown using stat -s for the
# same file, I arrived at this number for the offset.
dateoffset=3160520553
# if [ "$1" = "" ]; then
# filestoprocess=$PWD
# else
# filestoprocess=$1
# fi
for file in *; do
eval $(stat -s $file)
# will fix the modification date of each file
thismoddate=$st_mtime
echo "$file mod date = $thismoddate"
if [ $thismoddate -lt $cutoffdate ];
then
newmoddate=$(($thismoddate + $dateoffset))
date=`epochToGregor "$newmoddate"`
echo "$file 's mod date would be adjusted to $date"
touch -t $date $file
fi
doneOriginally I tried just handing the newmoddate to touch. And got some initally baffling results. I figured out what went wrong. Touch is looking for a non-Epoch formated date. Parsing that is sort of tricky with the months and leap-years and everything. As is, this doesn't work correctly, I suspect due to some weirdness with wanting to insert that "0" for single digit values. I poked around but I didn't find a simple way convert. I'm sure I missed something though. I'm hoping you or someone here can point me at something to convert Epoch seconds into YYYYMMDDhhmm.
assuna 03-16-2006, 04:51 AM hi orlando
i had the same problem... very annoying indeed. in my case the dates were 28 years in the future.
i wrote a bash script to fix the dates. you can download it here (http://assuna.com/downloads/setnewfiledate.txt). you can define year, month an day to add or subtract from the current date.
this was my first bash script, so it might not be written in a very elegant way, but it works.
however it did not fix all the dates, some creation dates did remain as they were. i don't know why.
in the end i found this utility (http://www.publicspace.net/ABetterFinderRename/), which fixed all of the dates. it costs only 20$ and is really worth it.
edit: to find the files i wanted to modificate i used spotlight.
hayne 03-16-2006, 11:01 AM Interestingly, the stat -s yielded a negative modification date. I'm not sure what that means other than something went seriously wrong.
A negative number of epoch seconds would indicate a time before 1970.
touch foo
eval $(stat -s foo)
rightnow=$st_mtime
echo "Right now in Epoch Time it's: $rightnow"
An easier way to get the current time in epoch seconds is:
date +%s
To get this into a variable, put that command inside back-quotes:
rightnow=`date +%s`
(Read 'man date' for details)
Touch is looking for a non-Epoch formated date. Parsing that is sort of tricky with the months and leap-years and everything. As is, this doesn't work correctly, I suspect due to some weirdness with wanting to insert that "0" for single digit values. I poked around but I didn't find a simple way convert. I'm sure I missed something though. I'm hoping you or someone here can point me at something to convert Epoch seconds into YYYYMMDDhhmm.
Here's a Perl script that will convert an epoch-time into a date in the format expected by 'touch -t'. For example, you could use it like this:
tdate=`epochToDate $newmodtime`
#!/usr/bin/perl
# epochToDate
# This script converts epoch time (seconds since January 1, 1970) to a date.
# It expects one command-line argument specifying an epoch time.
# It outputs the date corresponding to that epoch time in the format
# CCYYMMDDhhmm.SS expected by the 'touch' command.
# To get other formats, edit the printf statement in the script.
# Cameron Hayne (macdev@hayne.net) March 2006
use warnings;
use strict;
die "Usage: epochToDate time\n" unless scalar(@ARGV) == 1;
my $time = $ARGV[0];
die "Epoch time must be an integer\n" unless $time =~ /^-?\d+$/;
my ($sec, $min, $hour,
$day, $month, $year,
$wday, $yday, $isdst) = localtime($time);
$year += 1900;
$month += 1; # change to range 1..12 instead of 0..11
printf("%04d%02d%02d%02d%02d.%02d\n",
$year, $month, $day, $hour, $min, $sec);
But an alternative to using that script to convert to the format required by 'touch -t' would be to use the Perl function 'utime' to change the modification date. This function expects 3 arguments. The first two arguments are the desired access and modification times (in epoch seconds). The third argument is the name of the file to be "touched".
In a Perl script, you would use it like this:
utime($atime, $mtime, $filename);
You could use it from your shell script via the following one-line Perl invocation:
perl -e "utime($atime, $mtime, $filename)"
I.e. the above would be equivalent to the following:
mdate=`epochToDate $mtime`
touch -t $mdate $filename
except that (if I recall correctly) 'touch' sets the access time to the current time.
|
|
|
|
|