How to set file last modified file attribute in Perl - perl

Here I use last_run file stored in the local directory and update it's last modified date if it's not equal to the current datetime. Is it possible not to write a symbol into the file, but to update mtime in some other way?
I saw that there is a module File::Touch, but I don't want to use it as I already have an opened file, and it will be faster to use the file descriptor as is.
#!/usr/bin/perl
use utf8;
use strict;
use warnings;
use File::stat;
use DateTime;
my $file_name = "last_run";
if (-e $file_name)
{
my $fh;
if (open($fh, "+<", $file_name))
{
my $timestamp = stat($fh)->mtime;
my $now = DateTime->now(time_zone => 'local');
my ($sec, $min, $hour, $day, $month, $year) = localtime($timestamp);
$month += 1;
$year += 1900;
if ($now->day != $day or $now->month != $month or $now->year != $year)
{
print $fh ' '; # <- I have to write something into the file
}
print "$day $month $year\n";
print $now->day.' '.$now->month.' '.$now->year."\n";
}
else
{
print "cannot open +< $file_name: $!";
}
close($fh);
}
else
{
open(my $fh, ">", $file_name)
or print "cannot open > file name: $!";
}

You're looking for the utime function.
Some quotes from the documentation:
Changes the access and modification times on each file of a list of files. The first two elements of the list must be the NUMERIC access and modification times, in that order. Returns the number of files successfully changed. The inode change time of each file is set to the current time.
Since Perl 5.8.0, if the first two elements of the list are undef, the utime(2) syscall from your C library is called with a null second argument. On most systems, this will set the file's access and modification times to the current time.
On systems that support futimes(2), you may pass filehandles among the files. On systems that don't support futimes(2), passing filehandles raises an exception. Filehandles must be passed as globs or glob references to be recognized; barewords are considered filenames.
So, something like:
utime undef, undef, $fh;
will update the file's modification (And access) time to the current one. If you're not using most systems, there's another example in the documentation that explicitly uses the current time.
futimes(2) is present on Linux and BSDs. If you're using a different OS, you'll have to use the filename instead of handle.

Related

date creation with Image::Exiftools or Date::Handler

I'm trying to merge all dirs containing pictures having different timeZone, in a single dir, uniform file's names with a same format (Hungarian style yyyymmdd_hhmmss) eliminating huge of duplicates, at once.
I was looking for modules (over the stat statement which return only the epoch (stat(file))[9]) that can read the file's creation date, not the last modification or access date.
I fount Immage::ExifTools seem to be the effective and easiest to list.
I noted that after several cycling into the dir the module stop to extract the correct date/time output stuck in a monotone identical wrong date/time value for the next over files. Here is the list and below one of pictures that get me into this trouble: more precisely file's property on win10 give a creation date of 20140626 12:16 pm; running instead the routine I obtain 20021208_120000.
foreach $img(#img){
next if -l $img;
$img =~ /.+(\..+$)/;
$ext = $1;
# %ENV;
$exif=new Image::ExifTool;
$exif->ExtractInfo($dir.$img);
$for = $exif->GetValue('CreateDate');
$for =~ s/$space/\_/g;
$for =~ s/\://g;
$for = '_'.$for;
$size = (stat($dir.$img))[7];
# $date = Date->new($date[9]);
# #data = $date->array;
#tie my %date, 'Date::Tie', utc_epoch => $date{$date[9]}; #tz => $date{tz};
#my $date = Date::Tie->new( epoch => $date[9] );
%date;
# $for = IMG.$for.$ext;
if (!$all{'IMG'.$for.$ext}){
$all{'IMG'.$for.$ext}= $size ;
rename $dir.$img, $dir.'IMG'.$for.$ext;
print "rename $dir.$img, $dir.'IMG'.$for.$ext\n";
}elsif($all{'IMG'.$for.$ext} == $size){
unlink $dir.$img;
print "Deleting $dir.$img\n";
}
Checking the files properties involved, the "wrong" ones seems to have the same properties "working" ones: both working and wrong state the properties of : creation/acquisition date, modification date and last access date..
I can't understand where the module fault.
Have you any recommendation ? Any different module to use ?
Thanks
Simon
On Windows you can use Win32API::File::Time to read and modify the file creation time:
use feature qw(say);
use strict;
use warnings;
use Win32API::File::Time qw(GetFileTime SetFileTime);
use File::Temp qw(tempdir);
my $dir = tempdir( CLEANUP => 1 );
my $fn = 'test.txt';
open (my $fh, '>', $fn) or die "Could not open file '$fn': $!";
say $fh "Foobar";
close $fh;
print_file_times($fn);
my ($atime, $mtime, $ctime) = GetFileTime ($fn);
SetFileTime ($fn, undef, undef, $ctime-180);
print_file_times($fn);
sub print_file_times {
my ($fn) = #_;
my ($atime, $mtime, $ctime) = GetFileTime ($fn);
say "File: $fn:";
say " File access time: $atime";
say " File modification time: $mtime";
say " File creation time: $ctime";
return $ctime;
}
Output:
File: test.txt:
File access time: 1614640101
File modification time: 1614640101
File creation time: 1614639958
File: test.txt:
File access time: 1614640101
File modification time: 1614640101
File creation time: 1614639778

How can I count the number of times a pattern appears

I am trying to count the number of occurrences that a particular string appears in a text document so that the string can be deleted if it occurs less than 5 times. The text file has a list of dates that are formatted as 2015-06-16 07:40:00.
After processing the incoming data from 2015-06-16 07:40:00 to 2015061607, I want to count the number of times that this string appears. I have the processing of the incoming data correct but I don't know how to count the occurrences of the string.
This is what I have so far.
#!/usr/bin/perl
foreach $file (#ARGV) {
open (OUT, ">/d2/aschwa/scripts_and_programs/NST_Scripts/data_organizers/Filtered_$file") || die "Cannot open specified file\n";
open (RAW, "/d2/aschwa/scripts_and_programs/NST_Scripts/data_organizers/$file") || die "Cannot open specified file to be processed\n";
while(<RAW>) {
$event = $_;
chop($event);
#event = split (',', $event);
($date_time, $var1, $var2, $var3) = #event[(0,1,2,3)];
#date_time = split (' ', $date_time);
($date, $time) = #date_time[(0,1)];
#date_mod = split ('-', $date);
($year, $month, $day) = #date_mod[(0,1,2)];
#time = split (':', $time);
($hr, $mins, $sec) = #time[(0,1,2)];
$datehr = $year . $month . $day . $hr;
foreach ($event) {
$count{$datehr}++;
}
}
}
I think you should use a regular expression rather than repeated calls to split
This example reads through each file twice, counting the dates on the first pass and printing those lines whose dates appear more than five times on the second
It doesn't compress the date-time as you have done, but captures just up to the hour field, so the keys of the %count hash look like 2015-06-16 07. It is only extra code to remove the punctuation and isn't necessary to make the program work
I've also used autodie, which has been available since v5.10 of Perl and automatically checks the status of open and chdir calls for you. It is also best practice to use lexical file handles and the three-parameter form of open
#!/usr/bin/perl
use strict;
use warnings;
use 5.010;
use autodie;
use constant DIR => '/d2/aschwa/scripts_and_programs/NST_Scripts/data_organizers';
chdir DIR;
for my $file ( #ARGV ) {
open my $raw_fh, '<', $file;
my %count;
while ( <$raw_fh> ) {
++$count{$1} if /^(\d\d\d\d-\d\d-\d\d \d\d):\d\d:\d\d/;
}
seek $raw_fh, 0, 0; # Rewind input file
open my $out_fh, '>', "Filtered_$file";
while ( <$raw_fh> ) {
print unless /^(\d\d\d\d-\d\d-\d\d \d\d):\d\d:\d\d/ and $count{$1} < 5;
}
}

Find a string in one file and append part of it to matching line in another file

I have two files
first:
8237764738;00:78:9E:EE:CA:6F;FTTH;MULTI
8237764738;2C:39:96:52:47:82;FTTH;MULTI
0415535921;E8:BE:81:86:F1:6F;FTTH;MULTI
0415535921;2C:39:96:5B:12:C6;EZ;SINGLE
...etc
second:
00:78:9E:EE:CA:6F;2013/10/28 13:37:50
E8:BE:81:86:F1:6F;2013/11/05 13:38:30
00:78:9E:EC:4A:B0;2013/10/28 13:59:16
2C:E4:12:AA:F7:95;2013/10/31 13:57:55
...etc
and I have to take mac_address (second position) from the first file and find it in the second one
and append (if match) to first file the date at end from the second file.
output:
8237764738;00:78:9E:EE:CA:6F;FTTH;MULTI;2013/10/28 13:37:50
0415535921;E8:BE:81:86:F1:6F;FTTH;MULTI;2013/11/05 13:38:30
I write a simple script to find the mac_address
but I don't know how to put in the script to add the date.
my %iptv;
my #result;
open IN, "/home/terminals.csv";
while (<IN>) {
chomp;
#wynik = split(/;/,$_);
$iptv{$result[1]} = $result[0];
}
close IN;
open IN, "/home/reboots.csv";
open OUT, ">/home/out.csv";
while (<IN>) {
chomp;
my ($mac, $date) = split(/;/,$_);
if (defined $iptv{$mac})
{
print OUT "$date,$mac \n";
}
}
close IN;
close OUT;
Assuming that the first file lists each MAC number once and that you want an output line for each time the MAC appears in the second file, then:
#!/usr/bin/env perl
use strict;
use warnings;
die "Usage: $0 terminals reboots\n" unless scalar(#ARGV) == 2;
my %iptv;
open my $in1, '<', $ARGV[0] or die "Failed to open file $ARGV[0] for reading";
while (<$in1>)
{
chomp;
my #result = split(/;/, $_); # Fix array used here
$iptv{$result[1]} = $_; # Fix what's stored here
}
close $in1;
open my $in2, '<', $ARGV[1] or die "Failed to open file $ARGV[1] for reading";
while (<$in2>)
{
chomp;
my ($mac, $date) = split(/;/,$_);
print "$iptv{$mac};$date\n" if (defined $iptv{$mac});
}
close $in2;
This uses two file names on the command line and writes to standard output; it is a more general purpose program than your original. It also gets me around the problem that I don't have a /home directory.
For your sample inputs, the output is:
8237764738;00:78:9E:EE:CA:6F;FTTH;MULTI;2013/10/28 13:37:50
0415535921;E8:BE:81:86:F1:6F;FTTH;MULTI;2013/11/05 13:38:30
You were actually fairly close to this, but were making some silly little mistakes.
In your code, you either aren't showing everything or you aren't using:
use strict;
use warnings;
Perl experts use both to make sure they don't make silly mistakes; beginners should do so too. It would have pointed out that #wynik was not declared with my and was assigned to but not used, for example. You could have meant to write #result = split...;. You were not saving the correct data; you were not writing out the information from the $iptv{$mac} that you needed to.

How do I change values with perl and regex/sed inside a file?

I'm pretty sure I am doing something stupid and I apologize for this ahead of time. I have looked at the one-liners that were suggested elsewhere on similar searches and I like the idea of them, I'm just not sure how to apply because it's not a direct swap. And if the answer is that this can't be done, then that is fine and I will script around that.
The problem: I have log files I need to send through a parser that requires the dates to be in YYYY-MM-DD. The files can be saved this way; however, some people prefer them in YYYY/MM/DD for their own viewing and send those to me. I can modify one or two dates with sed and this works beautifully; however, when there are 2-3+ years in the files, it would be nice not to have to do it manually for each date.
My code (I have left the debugging commands in place):
use strict;
use File::Copy;
use Getopt::Std;
my %ARGS = ();
getopts('f:v', \%ARGS);
my $file = $ARGS{f};
&main();
sub main($)
{
open (FIN, "<$file") || die ("Cannot open file");
print "you opened the file\n";
while (<FIN>) {
my $line = $_;
if ($line =~ /(\d*)\/(\d*)\/(\d*) /i) {
#print "you are in the if";
my $year = $1;
my $month = $2;
my $day = $3;
print $line;
print "\nyou have year $1\n";
print "you have month $2\n";
print "you have day $3\n";
s/'($1\/$2\/$3)/$1-$2-$3'/;
}
}
close FIN;
}
I can see that the regex is getting the right values into my variables but the original line is not being replaced in the file.
Questions:
1) Should this be possible to do within the same file or do I need to output it to a different file? Looking at other answers, same file should be fine.
2) Does the file need to be opened in another way or somehow set to be written to rather than merely running the replace command like I do with sed? <--I am afraid that the failure may be in here somewhere simple that I am overlooking.
Thanks!
You never write to the file. With sed, you'd use -i, and you can do exactly the same in Perl.
perl -i -pe's{(\d{4})/(\d{2})/(\d{2})}{$1-$2-$3}g' file
Or with a backup:
perl -i~ -pe's{(\d{4})/(\d{2})/(\d{2})}{$1-$2-$3}g' file
That's equivalent to
local $^I = ''; # Or for the second: local $^I = '~';
while (<>) {
s{(\d{4})/(\d{2})/(\d{2})}{$1-$2-$3}g;
print;
}
If you didn't want to rely on $^I, you'd have to replicate its behaviour.
for my $qfn (#ARGV) {
open($fh_in, '<', $qfn)
or do { warn("Can't open $ARGV: $!\n"); next; };
unlink($qfn)
or do { warn("Can't overwrite $ARGV: $!\n"); next; };
open(my $fh_out, '>', $qfn) {
or do { warn("Can't create $ARGV: $!\n"); next; };
while (<$fh_in>) {
s{(\d{4})/(\d{2})/(\d{2})}{$1-$2-$3}g;
print $fh_out $_;
}
}
perl -pi.bak -e 's|(\d{4})/(\d\d)/(\d\d)|$1-$2-$3|g;' input
Replace input with your log file name. A backup file input.bak will be created in case you ever need the original data.

Generate dynamic file name list

I started programming in perl few months back and this is my first question on stackoverflow.com. I hope I can get a solution.
So I want to copy some files from an ftp server. The file names are in this format:
abc_201149_cde_07_fgh_include_internal
In this example the numeric part gets changed on weekly basis, e.g. 201149 says year = 2011 and week = 49. Similarly, 07 says which version it is.
I have copied all the file names into one file called "sdk_link.txt" and I am reading each file name from this and then copying to my local PC:
use Net::FTP;
use File::Copy;
$Login = "<redacted>";
$Pwd = "<redacted>";
$ftpHost = "<redacted>";
$ftpFolder = "/daj/dhakj/ahdakl/abc_201206_def_05";
$ftp=Net::FTP->new($ftpHost,Timeout=>100);
if ($ftp)
{
print $ftp->message;
}
$ftp->login($Login,$Pwd);
print $ftp->message;
$ftp->cwd($ftpFolder);
print $ftp->message;
open FILE,"sdk_link.txt" or die $!;
while($test=<FILE>)
{
chomp($test);
#Copy the file
copy("$test","/testing");
}
$ftp->quit;
I want to run this script every week on Windows. How can I make the numeric part change so that the correct files get downloaded?
Well, the obvious answer is to keep a template on file, and insert the correct numbers. For example:
echo abc_%s_cde_%s_fgh_include_internal |
perl -MPOSIX -nE 'say sprintf $_, strftime("%Y%U", localtime()), "07";'
Output:
abc_201207_cde_07_fgh_include_internal
So that if you'd have a file with templates, you can use %s to insert strings, and provide arguments either from your own list of arguments, or dynamically, as you prefer. E.g.:
my $year = "2011";
my $week = "49";
my $ver = "07"; # Strings or numbers does not really matter
open my $fh, '<', "sdk_link.txt" or die $!;
while (<$fh>) {
my $file = sprintf $_, $year, $week, $ver;
copy($file, "/testing") or die "Copy failed for file $file: $!";
}
I am not so sure File::Copy::copy works as intended for remote files, but that's another question. I believe Net::FTP::get() might be what you want.