Help with script to get backup log status on windows systems

M

Matt Williamson

The purpose of this script is to quickly get a status on all of my nightly
backups. It's working for everything but the .xml backup files. There are
multiple .xml files created each night for the various processes that occur,
the actual backup log is only one of them. I've determined that the .xml
backup log files that I want are all encoded utf16le and the others are
utf8. I only want to read the utf16le .xml files and print the server name
and filename for just the latest of those but I can't figure out the best
way to do it. Also, if you have any suggestions on the script in general,
please feel free to comment. I'm new at this, so any advice from experienced
coders is welcome and appreciated.

use strict;
use warnings;

my @content;
my @header;

my %belogdirs = (
"\\\\Server1" => "\\d\$\\Backup Exec\\data\\bex*.txt",
\\\\Server2 => "\\c\$\\Program Files\\Seagate Software\\Backup
Exec\\Nt\\Data\\bex*.txt",
\\\\Server3 => "\\c\$\\Program Files\\Veritas\\Backup
Exec\\Nt\\Data\\bex*.txt",
\\\\Server4 => "\\c\$\\Program Files\\Veritas\\Backup
Exec\\Nt\\Data\\bex*.txt",
\\\\Server5 => "\\c\$\\Program Files\\Veritas\\Backup
Exec\\Nt\\Data\\bex*.txt",
\\\\Server6 => "\\c\$\\Program Files\\Veritas\\Backup
Exec\\Nt\\Data\\bex*.xml"
);

open FILE, ">C:\\Backup Tape Log.txt";
foreach my $server (keys %belogdirs) {
my $fullpath = $server.$belogdirs{$server};
my @files = `dir "$fullpath" /OD /B`;
foreach my $file (reverse @files) {
$fullpath =~ s/bex\*\.xml|bex\*\.txt/$file/i;
chomp $fullpath;
open F, $fullpath or die "can't open $file: $!\n";
read F, my $buffer, 2;
@header = unpack "h*", $buffer;
close F;

if ($header[0] =~ /ffef/i) { #handle the utf16 .xml files
open F, "<:encoding(utf16le)", $fullpath or
die "can't open $file: $!\n";
@content = <F>;
close F;
}
elsif ($header[0] =~ /efff/i) { # just in case?
open F, "<:encoding(utf16be)", $fullpath or
die "can't open $file: $!\n";
@content = <F>;
close F;
}
else { # handle the .txt files
open F, $fullpath or
die "can't open $file: $!\n";
@content = <F>;
close F;
}

# $server =~ s/\\\\/Server: /;
# print FILE $server, "\n";
# print FILE "Log File: ",$fullpath,"\n";

foreach my $line (@content){
if ($line =~ /(job (?:started|ended|completion status)):\s*(.*?)\s*$/i) {
my ($job_type, $status) = ($1, $2);
print FILE "$job_type: $status\n";
}
}
print FILE "\n";
last;
}
}

close FILE;
exec("notepad C:\\Backup Tape Log.txt");


TIA

Matt
 
M

Matt Williamson

So, getting the first 2 bytes of the file, converting them to hex and
checking the string representation of the hex characters isn't the right way
to do it? That part seems to work fine. What is a better way?
It took me 4 hours to come up with that <g>

Matt

A. Sinan Unur said:
if ($header[0] =~ /ffef/i) { #handle the utf16 .xml files

I can't comment on the rest of your post, but here you are looking for a
sequence of characters 'f', 'f', 'e', 'f' anywhere in the string. Those
characters have nothing to do with the BOM.

http://www.unicode.org/faq/utf_bom.html#BOM

Sinan

--
A. Sinan Unur <[email protected]>
(remove .invalid and reverse each component for email address)

comp.lang.perl.misc guidelines on the WWW:
http://augustmail.com/~tadmc/clpmisc/clpmisc_guidelines.html
 
B

Ben Morrow

Quoth "Matt Williamson said:
The purpose of this script is to quickly get a status on all of my nightly
backups. It's working for everything but the .xml backup files. There are
multiple .xml files created each night for the various processes that occur,
the actual backup log is only one of them. I've determined that the .xml
backup log files that I want are all encoded utf16le and the others are
utf8. I only want to read the utf16le .xml files and print the server name
and filename for just the latest of those but I can't figure out the best
way to do it. Also, if you have any suggestions on the script in general,
please feel free to comment. I'm new at this, so any advice from experienced
coders is welcome and appreciated.

use strict;
use warnings;

Good :).
my @content;

This variable is not needed at this scope. You should declare it inside
the first loop, as that's where you use it.
my @header;

This variable is only needed per-file, so you should declare it
per-file. But see below for why I don't think you need it at all... :)
my %belogdirs = (
"\\\\Server1" => "\\d\$\\Backup Exec\\data\\bex*.txt",
\\\\Server2 => "\\c\$\\Program Files\\Seagate Software\\Backup
Exec\\Nt\\Data\\bex*.txt",
\\\\Server3 => "\\c\$\\Program Files\\Veritas\\Backup
Exec\\Nt\\Data\\bex*.txt",
\\\\Server4 => "\\c\$\\Program Files\\Veritas\\Backup
Exec\\Nt\\Data\\bex*.txt",
\\\\Server5 => "\\c\$\\Program Files\\Veritas\\Backup
Exec\\Nt\\Data\\bex*.txt",
\\\\Server6 => "\\c\$\\Program Files\\Veritas\\Backup
Exec\\Nt\\Data\\bex*.xml"
);

This would be easier with forward slashes and single quotes:

'//Server1' => '/d$/Backup Exec/data/bex*.txt',
open FILE, ">C:\\Backup Tape Log.txt";

It is generally safer to use lexical FHs and three-arg open, and you
should *always* check the return value:

open my $FILE, '>', 'c:/Backup Tape Log.txt'
or die "can't open 'c:/Backup Tape Log.txt': $!";
foreach my $server (keys %belogdirs) {
my $fullpath = $server.$belogdirs{$server};
my @files = `dir "$fullpath" /OD /B`;

OK, now you'll hit the only snag of using forward slashes: cmd.exe and
most native NT commands don't like it :). You can either convert, with
either a simple s!/!\\!g or with File::Spec::Functions::canonpath, or
(probably better) you can use (I guess) glob to do the globbing in Perl.
(I'm not entirely sure glob will do what you want, as I don't know what
/OD /B means to dir, but you can certainly emulate it with either glob
or File::Find.)
foreach my $file (reverse @files) {
$fullpath =~ s/bex\*\.xml|bex\*\.txt/$file/i;
chomp $fullpath;

Ick. This will work, I guess, but it's pretty nasty. I think at this
point I'd do the whole thing a little differently, starting with a
datastructure more like

my %belogdirs = (
Server1 => {
path => 'd$/Backup Exec/data',
extn => 'txt',
},
...
Server6 => {
dir => 'c$/Program Files/Veritas/Backup Exec/Nt/Data',
extn => 'xml',
},
);

for (keys %belogdirs) {
my $glob = "//$_/$belogdirs{$_}{path}/bex*.$belogdirs{$_}{extn}";

Actually, although I normally hate it (in Perl), that would probably be
clearer as a sprintf:

my $glob = sprintf '//%s/%s/bex*.%s' =>
$_, $belogdirs{$_}{path}, $belogdirs{$_}{extn};

for (reverse glob $glob) {
open my $F, '<', $_ or die ...;

Note that glob returns full paths, rather than just a list of names.
open F, $fullpath or die "can't open $file: $!\n";
read F, my $buffer, 2;
@header = unpack "h*", $buffer;

Why are you using an array when you only have one return value?
close F;

if ($header[0] =~ /ffef/i) { #handle the utf16 .xml files

You're not doing a pattern match here, you are checking for equality.
But you don't need to: if you can be sure your XML files will always
have a BOM, you can simply open the file :encoding(utf16). This will
throw an exception on the first read if there isn't a BOM. So,
continuing from the above:

for my $file (reverse glob $glob) {
my $mode;
$belogdirs{$_}{extn} eq 'xml'
and $mode = ':encoding(utf16)';

# I don't really like file extensions, but if you've got
# 'em you might as well use 'em...

open my $F, "<$enc", $file
or die "can't open '$file': $!";

eval {
@content = <$F>;
1;
} or next;
}

This will skip onto the next file if the read throws an exception.
open F, "<:encoding(utf16le)", $fullpath or
die "can't open $file: $!\n";
@content = <F>;
close F;
}
elsif ($header[0] =~ /efff/i) { # just in case?
open F, "<:encoding(utf16be)", $fullpath or
die "can't open $file: $!\n";
@content = <F>;
close F;
}
else { # handle the .txt files
open F, $fullpath or
die "can't open $file: $!\n";
@content = <F>;
close F;
}

# $server =~ s/\\\\/Server: /;
# print FILE $server, "\n";
# print FILE "Log File: ",$fullpath,"\n";

foreach my $line (@content){
if ($line =~ /(job (?:started|ended|completion status)):\s*(.*?)\s*$/i) {
my ($job_type, $status) = ($1, $2);
print FILE "$job_type: $status\n";

If you set $\ = "\n" then Perl'll print them for you.

It really helps a lot to keep you indentation sane.
print FILE "\n";
last;

I don't understand what this is here for?
}
}

close FILE;
exec("notepad C:\\Backup Tape Log.txt");

Does that work? It didn't ought to: the filename has spaces in it. I
would recommend using exec LIST so Perl does the nasty cmd.exe quoting
for you, even though it's not strictly as safe is it is on a real
platform:

exec notepad => 'c:\\Backup Tape Log.txt';

I would also put this filename in a variable. Never write the same thing
twice.

Ben
 
A

anno4000

A. Sinan Unur said:
A. Sinan Unur said:
if ($header[0] =~ /ffef/i) { #handle the utf16 .xml files

I can't comment on the rest of your post, but here you are looking
for a sequence of characters 'f', 'f', 'e', 'f' anywhere in the
string. Those characters have nothing to do with the BOM.

http://www.unicode.org/faq/utf_bom.html#BOM
So, getting the first 2 bytes of the file, converting them to hex and
checking the string representation of the hex characters isn't the
right way to do it? That part seems to work fine. What is a better
way? It took me 4 hours to come up with that <g>

I did not realize that was what you were doing.

It is a rather roundabout way of checking the first two bytes of
a string.

read F, my $buffer, 2;
@header = unpack "h*", $buffer;
if ($header[0] =~ /ffef/i) { #handle the utf16 .xml files

Instead, define a string constant that contains the relevant two
bytes and compare (untested):

use constant BOM => "\xFF\xEF";

read F, my $buffer, 2;
if ( $buffer eq BOM ) { # handle the utf16 .xml files

Anno
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,995
Messages
2,570,231
Members
46,820
Latest member
GilbertoA5

Latest Threads

Top