#!/usr/bin/perl # # timestamp # # AUTHOR: # Dan Harkless # # COPYRIGHT: # This file is Copyright (C) 2010 by Dan Harkless, and is released under the # GNU General Public License . # # USAGE: # % timestamp [OPTION]... [FILE]... # % timestamp++ [OPTION]... FILE... # % timecopy [OPTION]... FILE1 FILE2... # # DESCRIPTION: # Outputs and manipulates timestamps on files. All timestamps are printed and # specified as UNIX-style integer seconds past the epoch (not as human- # readable dates and times -- use 'ls' and 'touch' for that). Note that while # the -c option copies both the last accessed and last modified timestamps, # the other options that set the timestamp set only the last modified # timestamp to the specified value, setting the last access timestamp to the # current time. # # When called on one or more files without any options (barring an optional -v # / --verbose), the integer last modified timestamp is output for each of the # specified files, one per line, in the format "FILENAME: TIMESTAMP". # # This script exits with status 1 in the case of any errors. A few errors # (mostly related to commandline usage) are fatal and are denoted by a second # error line being output stating "timestamp: Aborting.". Nonfatal errors are # output as they occur, but processing of the remaining files is not aborted. # If nonfatal errors have occurred, an additional error line stating # "timestamp: Error exit delayed from previous errors." (a la 'tar') will be # output at the end to ensure that the errors are not missed due to stdout # messages scrolling them off the screen. # # COMMANDLINE OPTIONS: # -a / --add SECONDS # Adds SECONDS to the last modified timestamp of the specified files. # SECONDS can be negative to achieve subtraction. # # As a special case, if a link to or copy of the 'timestamp' script is # created called 'timestamp++', calling it will be equivalent to calling # 'timestamp -a 1' on the specified files. # # -c / --copy # Copies the last modified *and* last accessed timestamps from the first # file specified on the commandline to the second (and optionally third, # etc.) specified file. # # As a special case, if a link to or copy of the 'timestamp' script is # created called 'timecopy', calling it will be equivalent to calling # 'timestamp -c' on the specified files. # # -i / --input # Reads stdin for filenames and last modified timestamps to set for them. # The format of the lines is the same as is output by 'timestamp' with no # options ("FILENAME: TIMESTAMP"). Note that filenames containing colons # are handled properly. # # -s / --set INTEGER_TIMESTAMP # Sets the last modified timestamps of the specified files to the specified # INTEGER_TIMESTAMP. # # -v / --verbose # Outputs verbose status messages to stdout. We use stdout rather than # stderr for this so that it's possible to redirect all errors (and nothing # else) to a file. Because of this, if you use -v when generating output # intended for 'timestamp -i' consumption (not a recommended mode of # operation), be sure to grep out any lines starting with "timestamp: " from # the saved file (note this will also delete any lines outputting timestamps # for files, like this script, called "timestamp"). # # EXAMPLES: # % timestamp # outputs current time as an integer # % timestamp file1 # outputs file1's last modified time # % timestamp -a 1 file1 # adds 1 to file1's timestamp # % timestamp++ file1 # ditto # % timestamp -c file1 file2 # copies file1's timestamp to file2 # % timecopy file1 file2 # ditto # % timestamp * > ../ts.out # saves timestamps of * to ../ts.out # % timestamp -i < ../ts.out # restores timestamps of * from ../ts.out # % timestamp -s 1273201658 file1 # sets file1's timestamp to 1273201658 # % timestamp --set 1273201658 -v file1 # ditto, verbosely # # TO DO: # Allow printing and setting (with options besides -c) of the last accessed # time. Note that utime() has to set both times simultaneously, so the only # two modes of operation that would make much sense are the current mode of # setting last modified to the specified integer and last accessed to the # current time, or else outputting/setting both of them simultaneously. # # Add an option like the 'patch' command's -p to strip leading directories # from paths. # # Add an option to replace characters that are illegal in Windows filenames # with '_' on output. # # DATE MODIFICATION # ========== ================================================================== # 2010-05-07 Output verbose messages to stdout rather than stderr. # 2010-05-06 Original (replaces my time{copy,set,stamp,stamp++}.c programs). ## Modules used ################################################################ use English qw(-no_match_vars); # allow use of names like @ARG rather than @_ use File::Basename; # for basename() use Getopt::Long; # for GetOptions() use warnings; # get warnings for this script but not modules ## Subroutines ################################################################# sub fatal_error { print STDERR @ARG, "\n"; print STDERR "$progname: Aborting.\n"; exit 1; } sub nonfatal_error { print STDERR @ARG, "\n"; $exit_status = 1; } sub verbose_message { if ($opt_verbose) { print "$progname: @ARG\n"; } } ## Main ######################################################################## $exit_status = 0; $ATIME_INDEX = 8; $MTIME_INDEX = 9; $progname = basename($PROGRAM_NAME); if (!GetOptions("add=i" => \$opt_add, "copy" => \$opt_copy, "input" => \$opt_input, "set=i" => \$opt_set, "verbose" => \$opt_verbose)) { fatal_error("Usage: $progname [OPTION]... [FILE]...\n" . " -a / --add SECONDS\n" . " -c / --copy\n" . " -i / --input\n" . " -s / --set INTEGER_TIMESTAMP\n" . " -v / --verbose"); } if ($progname eq "timecopy") { $opt_copy = 1; } elsif ($progname eq "timestamp++") { $opt_add = 1; } $number_of_files = scalar @ARGV; if (defined($opt_add) or defined($opt_copy) or defined($opt_set)) { if ((defined($opt_add) + defined($opt_copy) + defined($opt_input) + defined($opt_set)) > 1) { fatal_error("$progname: -a/--add, -c/--copy, -i/--input, and -s/--set" . " are mutually exclusive."); } elsif ($number_of_files == 0) { fatal_error("$progname: Filenames must be specified when using" . " -a/--add, -c/--copy, or -s/--set."); } elsif (defined($opt_copy) and $number_of_files < 2) { fatal_error("$progname: At least two filenames must be specified when" . " using -c/--copy."); } } elsif (defined($opt_input) and $number_of_files != 0) { fatal_error("$progname: With -i/--input, filenames can't be specified on" . " the commandline."); } if ($number_of_files == 0) { if (not defined($opt_input)) { verbose_message("Neither -i nor any files specified." . " Printing current timestamp."); print time(), "\n"; } else { verbose_message("Reading filenames and timestamps from stdin."); $had_at_least_one_line = 0; while () { $had_at_least_one_line = 1; $ARG =~ /^(.*?): (\d+)\s*$/; if (not (defined($1) and defined($2))) { nonfatal_error("$progname: Line on stdin not in 'FILENAME:" . " TIMESTAMP' format:\n$ARG"); } else { verbose_message("Setting last modification" . " timestamp of '$1' to $2."); if (not utime(time(), $2, $1)) { nonfatal_error("$1: $OS_ERROR."); } } } if (not $had_at_least_one_line) { fatal_error("$progname: -i/--input specified but nothing could be" . " read from stdin."); } } } elsif (defined($opt_copy)) { $orig_file = shift; @orig_file_stat = stat($orig_file); if (not @orig_file_stat) { nonfatal_error("$orig_file: $OS_ERROR."); } else { foreach $file (@ARGV) { verbose_message("Copying last modification timestamp" . " from '$orig_file' to '$file'."); if (not utime($orig_file_stat[$ATIME_INDEX], $orig_file_stat[$MTIME_INDEX], $file)) { nonfatal_error("$file: $OS_ERROR."); } } } } else { foreach $file (@ARGV) { @file_stat = stat($file); if (not @file_stat) { nonfatal_error("$file: $OS_ERROR."); } elsif (defined($opt_add)) { $old_time = $file_stat[$MTIME_INDEX]; $new_total = $old_time + $opt_add; verbose_message("Setting last modification timestamp" . " of '$file' to $old_time + $opt_add = " . " $new_total."); if (not utime(time(), $new_total, $file)) { nonfatal_error("$file: $OS_ERROR."); } } elsif (defined($opt_set)) { verbose_message("Setting last modification timestamp" . " of '$file' to $opt_set."); if (not utime(time(), $opt_set, $file)) { nonfatal_error("$file: $OS_ERROR."); } } else { print "$file: ", $file_stat[$MTIME_INDEX], "\n"; } } } if ($exit_status != 0) { nonfatal_error("$progname: Error exit delayed from previous errors."); } exit $exit_status;