Perl
Links
Template
#!/usr/bin/env perl # old: !/usr/bin/perl use strict; use warnings; # DESCRIPTION # My Perl Template # by Torben Menke https://entorb.net our $VERSION = '2020-01-24'; # TODO # IDEAS # DONE # Modules: My Default Set use 5.010; # say use utf8; # this script is written in UTF-8 use autodie qw (open close); # Replace functions with ones that succeed or die: e.g. close # use Data::Dumper; # use Time::HiRes( 'time' ); # -> time() -> float of seconds # pp -u -M Excel::Writer::XLSX -o script.exe script.pl & copy script.exe c:\tmp # Modules: Perl Standard use Encode qw(encode decode); # use open ':encoding(UTF-8)'; # default encoding for all files, not working for open( my $fhIn, '<', $fileIn ) # default encoding for print STDOUT if ( $^O eq 'MSWin32' ) { binmode( STDOUT, ':encoding(cp850)' ); } else { binmode( STDOUT, ':encoding(UTF-8)' ); } # Modules: File Access use File::Basename; # for basename, dirname, fileparse # use File::Path qw(make_path remove_tree); # Modules: CPAN # use LWP::UserAgent; # http requests # use Excel::Writer::XLSX; # perl -MCPAN -e "install Excel::Writer::XLSX" # Start my $s = "äöüß"; say $s; my $fileIn = "data.txt"; open my $fhIn, '<:encoding(UTF-8)', $fileIn or die "ERROR: Can't read from file '$fileIn': $!"; # 1. as array my @cont = <$fhIn>; # 2. as string, via slurp my $cont = do { local $/ = undef; <$fhIn> }; # 3. row by row while ( my $row = <$fhIn> ) { chomp $row; print "$row\n"; } close $fhIn; my ( $fname, $fdir, $fext ) = fileparse( $fileIn, qr/[.][^.]*/ ); my $fileOut = "$fdir/$fname-report$fext"; open my $fhOut, '>:encoding(UTF-8)', $fileOut or die "ERROR: Can't write to file '$fileOut': $!"; print { $fhOut } $s; close $fhOut;
Basics
Strings
SubString and Length
say length ($s); $s = substr( $s, 0, length( $s ) - 2 ); # remove last 2 char
Arrays
split and join
my @L = split /b/, 'abc'; my $s = join '-', @L;
remove item from array
my $i = 2; splice @L, $i, 1; # remove 1 item at position $i
min/max
use List::Util qw( min max ); use List::MoreUtils qw( minmax ); # performanter than List::Util if both are desired
Check if element in list/array, see [1] for many more solutions the this
my $thing = "asdf"; if ( not grep { $thing eq $_ } @L ) { say "$thing is not in list @L"; }
or
if (grep {$cgi->param( 'dateresolution' ) eq $_ } qw (all year quarter month) ) { $dateresolution = $cgi->param( 'dateresolution' ); }
Fill Array with 0
@L = (0) x (100); # 100 times -> last one is $L[99]
Sort
Reverse (sort) list / array
@list = reverse @list;
Sort numerically
@list = sort {$a <=> $b} @list;
Sort alphabetically
@list = sort {$a cmp $b} @list;
Sort by substring
@list = sort { my ( $aa, $bb ) = ( $a, $b ); # remove 'ID,' for comparison $aa =~ s/^\d+,//; $bb =~ s/^\d+,//; $aa cmp $bb; } @list ;
Sorting Hashes
my %h; # by values, reverse foreach my $k ( sort { $h{ $b } <=> $h{ $a } } keys %h ) { # last if $h{$k}==1; say "$h{$k}\t$k"; } # by keys foreach my $k ( sort keys %h ) { say "$k\t$h{$k}"; }
Sorting an Array of Hashes
Sort array of hashrefs by a certain hash key
my @sorted = sort { my ( $aRef, $bRef ) = ( $a, $b ); my %aHash = %{ $aRef }; my %bHash = %{ $bRef }; $aHash{ 'moving_time' } <=> $bHash{ 'moving_time' }; } @list;
Math
rounding:
my $float = 12.345; $float = 0 + sprintf '%.1f', $float;
integer rounding from [2]
sub round { my ( $f ) = @_; return int( $f + $f / abs( $f * 2 || 1 ) ); }
defined/undef
my $var; my @L; my %h; if ( not defined $var ) { say 'Var is undef'; } if ( not %h ) { say 'Hash is empty'; } if ( not exists $h{ 'key' } ) { say "Hash has no key 'key'"; } if ( not defined $h{ 'key' } ) { say "Hash key 'key' has undefined value"; } # shorter: say defined $var ? 'DEFINED' : 'NOT';
Undefine/delete
undef $var; # equals $var = undef; undef @L; # equals @L = () undef $L[ 2 ]; # or $L[ 2 ] = undef; splice @L, 2, 1; # remove 1 item at position 2 undef %h; # equals %h = () undef $h{ 'key' }; # or $h{ 'key' } = undef; delete $h{ 'key' }; # removes both: key and value
Regular Expressions
Look Ahead
s/String.*(?=\n)//sg;
Look Behind
s/(?<=\n)String.*//sg;
Use ! for negative look behind / backs
Timing
use Time::HiRes( 'time' ); # -> time() -> float of seconds my $tsStart = time; # ... say sprintf "%.1fs after performing XYZ", ( time - $tsStart );
Date
V1
# TimeStamp -> String my $datestr; @_ = localtime time; # ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime (time); $datestr = sprintf "%04d-%02d-%02d_%02d-%02d-%02d\n", $_[ 5 ] + 1900, $_[ 4 ] + 1, $_[ 3 ], $_[ 2 ], $_[ 1 ], $_[ 0 ]; # DE Format @_ = localtime time; $datestr = sprintf "%02d.%02d.%04d %02d:%02d:%02d", $_[ 3 ], $_[ 4 ] + 1, $_[ 5 ] + 1900, $_[ 2 ], $_[ 1 ], $_[ 0 ]; # String -> TimeStamp use Time::Local; my $timestamp = timelocal( $sek, $min, $h, $d, $m - 1, $y - 1900 ); sub convertTimeStamp2Date { my ( $ts ) = @_; @_ = localtime $ts; # ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime (time); my $datestr = sprintf "%02d.%02d.%04d", $_[ 3 ], $_[ 4 ] + 1, $_[ 5 ] + 1900; return $datestr; }
V2
use DateTime; sub reformatDate { # 01/Nov/2012:19:17:55 -0700 -> CET Timezone my ( $in ) = @_; $in =~ m/^(\d{2})\/([a-z]{2,3})\/(\d{4}):(\d{2}):(\d{2}):(\d{2}) ([+\-]?\d{4})/i or die " E: date strange;"; my ( $d, $m, $y, $h, $min, $s, $tzoffset ) = ( $1, $2, $3, $4, $5, $6, $7 ); my %mo = ( 'Jan' => '01', 'Feb' => '02', 'Mar' => '03', 'Apr' => '04', 'May' => '05', 'Jun' => '06', 'Jul' => '07', 'Aug' => '08', 'Sep' => '09', 'Oct' => '10', 'Nov' => '11', 'Dec' => '12' ); $m = $mo{ $m }; my $dt = DateTime->new( year => $y, month => $m, day => $d, hour => $h, minute => $min, second => $s, time_zone => 'America/Los_Angeles' ); $dt->set_time_zone( 'Europe/Berlin' ); my $dateStr = sprintf "%4d-%02d-%02d_%02d:%02d:%02d", $dt->year(), $dt->month(), $dt->day(), $dt->hour(), $dt->minute(), $dt->second(); return $dateStr; }
handling of iso8601 date format
use Date::Parse; my $datestr = "2021-05-14T09:22:21Z"; # iso8601 format say $datestr; # my ($ss,$mm,$hh,$day,$month,$year,$zone) = strptime($date); my $ts= str2time($datestr); use DateTime; # my $dt = DateTime->now()->iso8601().'Z'; my $dt = DateTime->from_epoch( epoch => $ts+10); say $dt->iso8601().'Z';
convert week and year -> date
use Date::Tie; tie my %dt, 'Date::Tie', year => 2018, week => 30, weekday => 5; say "$dt{day}.$dt{month}.$dt{year}"; say $dt{epoch}; # TimeStamp
Multidim Lists / References
Hash of hashes / multidim hast
my %hash; $hash{$status}{"Count"}{$knowMsg}++; ... foreach my $knowMsg (keys (%{$hash{$status}{"Count"}})) { ... } ... my $count = 0; $count = $hash{$status}{"Count"}{$knowMsg} if exists ($hash{$status}{"Count"}{$knowMsg});
Creation
$scalarref = \$foo; $arrayref = \@ARGV; $hashref = \%ENV; $coderef = \&handler; $globref = \*foo;
Modification
$hashref->{'key'} = 'value';
Readout / copying
@list = @{ $hash{ $keyx } }; # General: $s = ${ $scalarref }; @L = @{ $arrayref }; %h = %{ $hashref };
anonymous list: [ ... ]
$arrayref = [ 1, 2, 'a', 'b', 'c' ]; # $arrayref -> [2] == 'a' $arrayref = [ 1, 2, [ 'a', 'b', 'c' ] ]; # $arrayref -> [2][1] == 'b'
anonymous hash: { ... }
$hashref = { 'Adam' => 'Eve', 'Clyde' => 'Bonnie', };
Save an array inside a hash:
$hash{ $keyx } = \@list;
see perlref.html
Remove Row and Column from Symmetric Matrix
sub removeRowColFromMatrix { # removes row $i and col $i from symmetric matrix # in: $i, ref to @matrix # out: nothing, since working on ref to matrix my ( $i, $refMatrix ) = @_; # remove row $i splice @{ $refMatrix }, $i, 1; # remove col $i foreach my $row ( @{ $refMatrix } ) { next unless defined $row; my @L = @{ $row }; next if ( $#L < $i ); splice @L, $i, 1; $row = \@L; } return; } sub removeRowsColsFromMatrix { # removes multiple rows and columns from symmetric matrix # first the rows are removed (=fast), than columns are removed (=slow) from the remaining rows # $refIndicesToRemove MUST BE ORDERD DESC!!! # in: $refIndicesToRemove, $refMatrix # out: nothing, since working on ref to matrix my ( $refIndicesToRemove, $refMatrix ) = @_; foreach my $i ( @{ $refIndicesToRemove } ) { # remove row $i splice @{ $refMatrix }, $i, 1; } # remove col $i foreach my $i ( @{ $refIndicesToRemove } ) { foreach my $row ( @{ $refMatrix } ) { next unless defined $row; my @L = @{ $row }; next if ( $#L < $i ); splice @L, $i, 1; $row = \@L; } } return; } my @matrix = ( [ 'q', 'w', 'e', 'r', 't' ], [ 'q', 'w', 'e', 'r', 't' ], [ 'q', 'w', 'e', 'r', 't' ], [ 'a', 's', 'd', 'f', 'g' ], [ 'q', 'w', 'e', 'r', 't' ] ); removeRowColFromMatrix( 3, \@matrix ); print "@matrix"; @_ = ( 2, 4 ); removeRowsColsFromMatrix( \@_, \@matrix ); print "@matrix";
Store/read hash to/from file
from [3]
use Storable; store \%table, 'file'; my $hashref = retrieve( 'file' ); my %hash = %{ retrieve( 'file' ) }; # retrieve hash instead of hashref
Column-wise sum of arrays
from [4]
my @A2sum = ( [ 1, 0, 0, 0, 1 ], [ 1, 1, 0, 1, 1 ], [ 2, 0, 2, 1, 0 ] ); my @sums; foreach my $column ( 0 .. $#{ $A2sum[ 0 ] } ) { my $sum; foreach my $aref ( @A2sum ) { $sum += $aref->[ $column ]; } push @sums, $sum; }
Transpose Matrix
my @rows = (); my @transposed = (); # This is each row in your table push @rows, [ qw(0 1 2 3 4 5 6 7 8 9 10) ]; push @rows, [ qw(6 7 3 6 9 3 1 5 2 4 6) ]; for my $row ( @rows ) { for my $column ( 0 .. $#{ $row } ) { push @{ $transposed[ $column ] }, $row->[ $column ]; } }
Array Minus / Delta / Difference
V2: unique lists -> remove hits from hash to speed up checks
sub arrayMinus { # use @diff = arrayMinus(\@array1, \@array2) # returns A MINUS B my ( $refA, $refB ) = @_; my @A = @{ $refA }; my @B = @{ $refB }; # TODO: %hB reduzieren nach jeder Prüfung my %hB = map { $_ => 1 } @B; # my @diff = grep {not $hB{$_}} @A; my @diff; foreach my $item ( @A ) { if ( not exists $hB{ $item } ) { push @diff, $item; } else { delete $hB{ $item }; # remove item from hash for speeding up checks } } return @diff; }
V1: simple
sub arrayMinus { # use @diff = arrayMinus(\@array1, \@array2) # returns A MINUS B my ( $refA, $refB ) = @_; my @A = @{ $refA }; my @B = @{ $refB }; my %hB = map { $_ => 1 } @B; my @diff = grep { not $hB{ $_ } } @A; return @diff; } my @diff = arrayMinus( \@array1, \@array2 )
ANSIColor: Colorful Terminal Output
use Term::ANSIColor; print color 'bold blue'; print "This text is bold blue.\n"; print color 'reset';
Encoding
For console output in Windows
use utf8; #this script is written in UTF-8 use Encode qw(encode decode); my $encodingSTDOUT = 'CP850'; # Windows/DOS: 'CP850'; Linux: UTF-8 my $s = 'ÄÖÜäöüß'; print encode ( $encodingSTDOUT, $s );
for files
open( OUT, ">", "join.csv" ); binmode( OUT, ":UTF-8" ); binmode( OUT, ":encoding(Latin1)" );
or for all files
use open ":encoding(Latin1)";
When using pp to compile executeable files (.exe) the following flag is required:
pp ... -M PerlIO::encoding ...
On utf8, UTF-8 and their differences see [6]
# Using UTF-8 (in any case and with either a hyphen or underscore) is the strict, valid encoding and gives a warning for invalid sequences.
After correct decoding regular expression \w matches word chars like 'äöüß'.
Guess Encoding
use Encode::Guess qw/utf8 latin1 UTF-8/; my $decoder = guess_encoding( $data, 'UTF-8' ); $decoder = guess_encoding( $data, 'iso-8859-1' ) unless ref $decoder; die $decoder unless ref $decoder; printf "Decoding as %s\n\n", $decoder->name; $data = $decoder->decode( $data );
File-Access
chdir to dir of perl file
chdir dirname( __FILE__ );
Template/Snipping
see top of this page
Handling path, name, extension
use File::Basename; use File::Path qw(make_path remove_tree); my $file = '/tmp/asdf.txt'; my ( $fname, $fdir, $fext ) = fileparse( $file, qr/\.[^.]*/ ); $fext = "\L$fext"; # lower ext. my $fileOut = $fdir . 'backup/' . $fname . $fext;
make dir including full path/tree
$_ = dirname( $fileOut ); make_path $_ unless -d $_;
remove path/tree recursivly
remove_tree (dirname( $fileOut ));
Copy
use File::Copy; copy($source, $target);
Filesize
my $size = -s $file;
or
my $size = (stat($file))[7]
or
my ($device, $inode, $mode, $nlink, $uid, $gid, $rdev, $size, $atime, $mtime, $ctime, $blksize, $blocks) = stat($file);
Check OS
my $os; if ( $^O eq 'MSWin32' ) { $os = 'win'; } elsif ( $^O eq 'linux' ) { $os = 'lin'; } else { die "unknown OS: $^O\n"; }
Recursive file Access V2 / traverse
from [8]
my $path = shift || '.'; traverse( $path ); sub traverse { my @queue = @_; while ( @queue ) { my $thing = shift @queue; if ( -f $thing and $thing =~ m/\.jpe?g$/i ) { say $thing; # doSomething ($thing ); } next if not -d $thing; opendir my $dh, $thing or die; while ( my $sub = readdir $dh ) { next if $sub eq '.' or $sub eq '..'; push @queue, "$thing/$sub"; } closedir $dh; } return; }
Recursive file Access V1
from [9] Delete old files of ext .log in folder if file is older than 365 days
use Modern::Perl; use Path::Class; my @dirs = grep {-d} <*>; foreach ( @dirs ) { my $dir = dir( $_ ); $dir->traverse( sub { # Path::Class::Dir allows to dive / traverse into all children my ( $child, $cont ) = @_; if ( not $child->is_dir and $child->stat ) { # not is_dir -> file if ( $child =~ m/\.log$/i ) { # filter on file name / ext if ( $child->stat->mtime < ( time - 365 * 86400 ) ) { say "$child could be deleted"; # to delete # unlink $child; } else { # say $child . " : " . sprintf( "%.2f", ( time - $child->stat->ctime ) / 86400 ) . " days"; } } # filename match } # is a file return $cont->(); # returns children for iteration } ); }
Compile Perl to .exe (Windows)
perl -MCPAN -e "install PAR::Packer" pp -o script.exe script.pl # include mudules via -M or -a pp -M String::Similarity -a "../lib/TM/TestModuleMath.pm" -o script.exe script.pl
Error "Can't locate utf8_heavy.pl": If UTF-8 is used set
pp -u ...
DLL libexpat-1__.dll missing
pp -l "libexpat-1__.dll"
Zip files
use IO::Compress::Zip qw(zip $ZipError) ; my $pathToZip = "Activities.zip"; my @jsonFiles = </activityList/*.json>; zip \@jsonFiles => $pathToZip , FilterName => sub { s<.*[/\\]><> } # trim path -> filename only , TextFlag => 1 # It is used to signal that the data stored in the zip file/buffer is probably text. , CanonicalName => 1 # This option controls whether the filename field in the zip header is normalized into Unix format before being written to the zip file. , ZipComment => "Created by Torben" # comment, just for fun , Level => 7 # [0..9], 0=none, 9=best compression or die "zip failed: $ZipError\n";
or
use Archive::Zip; my $zip = Archive::Zip->new(); $zip->addTree( '.' ); $zip->writeToFileNamed( '../slide1.zip' );
Unzip
use Archive::Extract; my $ae = Archive::Extract->new( archive => $fileZip ); my $ok = $ae->extract( to => $dirOut ); if ( not $ok == 1 ) { die "E: $fileZip not unzipped "; }
Sendmail
sub send_mail { # in: $subject, $body, $to_address # out: nothing # send an email my ( $subject, $body, $to_address ) = @_; $subject = encode( 'UTF-8', $subject ); $body = encode( 'UTF-8', $body ); my $mailprog = '/usr/lib/sendmail'; open( my $fh, "| $mailprog -t" ) || print { *STDERR } "Mail-Error\n"; print { $fh } "To: $to_address\n"; print { $fh } "Subject: $subject\n"; print { $fh } "Content-Type: text/plain; charset=\"utf-8\"\n"; print { $fh } "\n$body"; # \n starts body close $fh; return; } ## end sub send_mail
Jpeg Image Meta Data EXIF + IPTC
use strict; use warnings; use 5.010; # say use Data::Dumper; my $file = '181226_190440_TME_2285.jpg'; die "E: can't find/open file '$file'" unless -f $file; # EXIF Info use Image::EXIF; my $exif = Image::EXIF->new( $file ) or die $!; print Dumper $exif; my $exif_info = $exif->get_all_info(); # get_all_info() # get_image_info() # get_camera_info() # get_other_info() # get_point_shoot_info() # get_unknown_info() print Dumper $exif_info; my %h = %{ $exif_info }; my $imageDate = $h{ 'other' }{ 'Image Generated' }; # 2018:09:29 11:46:41 say $imageDate; # IPTC Info use Image::IPTCInfo; # Create new iptcinfo object my $iptc_info = new Image::IPTCInfo( $file ) or die $!; print Dumper $iptc_info; # Check if file had IPTC data unless ( defined( $iptc_info ) ) { die Image::IPTCInfo::Error(); } print Dumper $iptc_info->Keywords(); print Dumper $iptc_info->SupplementalCategories(); print Dumper $iptc_info->Contacts(); my $keywordsRef = $iptc_info->Keywords(); # Keywords, SupplementalCategories, or Contacts
Read Excel
See https://metacpan.org/pod/Spreadsheet::Read and https://perlmaven.com/read-an-excel-file-in-perl
# pp -u -l "libexpat-1__.dll" -M Spreadsheet::Read -M Spreadsheet::ParseExcel -M Spreadsheet::ParseXLSX -o script.exe script.pl use Spreadsheet::Read qw(ReadData); # perl -MCPAN -e "install Spreadsheet::Read" use Encode; my $file = "Test.xls"; my ( $fname, $fdir, $fext ) = fileparse( $file, qr/\.[^.]*/ ); my $fileOut = "$fname.txt"; open my $fh, '>:encoding(UTF-8)', $fileOut; my $book = ReadData( $file ); say { $fh } "Kunde:\t" . $book->[ 1 ]{ B3 }; close $fh; # iterate over all cells my @rows = Spreadsheet::Read::rows( $book->[ 1 ] ); my @row = @{ $rows[ $i - 1 ] }; # replace undef -> @row = map { ( defined $_ ? $_ : ) } @row; foreach my $i ( 1 .. scalar @rows ) { foreach my $j ( 1 .. scalar @{ $rows[ $i - 1 ] } ) { say chr( 64 + $i ) . "$j " . ( $rows[ $i - 1 ][ $j - 1 ] // ); } }
Create Excel
see https://perlmaven.com/create-an-excel-file-with-perl see https://metacpan.org/pod/Spreadsheet::WriteExcel::Examples#Example:-autofit.pl for an auto-scale-column-width hack
# pp -u -l "libexpat-1__.dll" -M Spreadsheet::Read -M Spreadsheet::ParseExcel -M Spreadsheet::ParseXLSX -M Excel::Writer::XLSX -o script.exe script.pl use Excel::Writer::XLSX; # perl -MCPAN -e "install Excel::Writer::XLSX" my $workbook = Excel::Writer::XLSX->new( 'report.xlsx' ); my $worksheet = $workbook->add_worksheet(); $workbook->set_properties( title => 'This is a title', author => 'Torben Menke', comments => 'Created with Perl and Excel::Writer::XLSX', ); # Add a handler to store the width of the longest string written to a column. # We use the stored width to simulate an autofit of the column widths. # # You should do this for every worksheet you want to autofit. $worksheet->add_write_handler(qr[\w], \&store_string_widths); $s = \@Reihenfolge; my $line = 0; # starts at 0 $worksheet->write( $line, 0, $s ); # header row ... @L = map { $h{$_} } @Reihenfolge; $s = \@L; $line ++; $worksheet->write($line, 0, $s ); # data row ... # Run the autofit after you have finished writing strings to the workbook. autofit_columns($worksheet); # from # https://metacpan.org/pod/Spreadsheet::WriteExcel::Examples#Example:-autofit.pl $workbook->close;
Date and Time Cells (write_date_time)
my $date_format = $workbook->add_format( num_format => 'dd.mm.yyyy hh:mm:ss' ); # for *display* in Excel @_=localtime($ts); #($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime (time); my $datestr = sprintf "%4d-%02d-%02dT%02d:%02d:%02dZ ",$_[5]+1900,$_[4]+1,$_[3],$_[2],$_[1],$_[0]; # !!! ISO8601 format, required for Excel !!! # 2018-08-28 or 2018-08-28T14:24:22+00:00 or 2018-08-28T14:24:22Z or 20180828T142422Z $worksheet->write_date_time( $row, $col, $datestr , $date_format )
GUI - TK - File Open Dialog
use Tk; my $mw = MainWindow->new(); $mw->title( "Some Window" ); my $infile = $mw->getOpenFile(); # getSaveFile() print "$infile\n";
Catch CRTL+C/STRG+C
$SIG{INT} = \&interrupt; # catch STRG+C # ... sub interrupt { say "Caught a control c!"; # ... exit; }
Options / Commandline Parameters
Help: Cpan Nice Article
use Getopt::Long;
var 1
my %o; # stores command line parameters/options $o{ 'trans' } = 1; # defaults $o{ 'logname' } = ""; $o{ 'sleep' } = 1.3; my $res = GetOptions( \%o, "trans|t!", "logname|n=s", "sleep|s=f" ); if ( $o{ 'logname' } eq "" ) { say " Parameters: --logname -n NAME = name of logfile --trans = enable/disable, default = enable --sleep -s x = Sleep/wait x sec between each ping, default: 1 sec "; exit 1; # 0 = ok, 1 = error }
var2
use Getopt::Long; my %o; # stores command line parameters/options $o{ 'trans' } = 1; # defaults $o{ 'gray' } = 0; my $res = GetOptions( \%o, "trans|t!", "trim!", "gray|grey|g!" ); # simple flag: 1/0 my $debug = 0; # set default value for option $res = GetOptions( "debug" => $debug ); print "Debug flag is $debug"; # ./script.pl --debug # read options my $age; $res = GetOptions( "age=i" => $age ); # i for int ; s for string if ( $age ) { print "Input age is $age years"; } # ./script.pl --age=89 # optional options # use ":" instead of "=" $res = GetOptions( "age:i" => $age ); # multiple names for one option my $color; $res = GetOptions( "color|colour|c" => $color ); # yes / no value my $counter = -1; # default value $res = GetOptions( "counter!" => $counter ); # ./script.pl --counter -> counter is 1 # ./script.pl --nocounter -> counter is 0 # hashing it $res = GetOptions( \%o, "base=i", "height=i" ); print $o{ 'base' };
Create a Perl Module for code reuse
use, require, do
use ModuleName; # prefered way, use statements happen at compile time and throw an exception if they fail. Allows for importing functions into the current namespace require ModuleName; # 'require' happens at run-time, and 'use' happens and compile-time require 'file.pl'; # same do 'file.pl'; # execute file as Perl script, similar to eval but in the same namespace. used for configurations.
[10]: Standard practice is to use use most of the time, require occasionally, and do rarely.
example1
main.pl
use lib ('./'); use TMsConfig qw( %o ); print %o;
TMsConfig.pm
package TMsConfig; use Exporter qw(import); # gives you Exporter's import() method directly -> use for exporting variables via our @EXPORT our @EXPORT = qw( %o ); our %o; $o{'para1'} = 42; 1; # Module needs to return 1
example2
from [11]
.../lib/TM/TestModuleMath.pm
package TM::TestModuleMath; use strict; use warnings; use Exporter qw(import); our $var = 42; our @EXPORT = qw ($var); # put stuff here you want to export our @EXPORT_OK = qw(add multiply); # will be exported on request sub add { my ($x, $y) = @_; return $x + $y; } sub multiply { my ($x, $y) = @_; return $x * $y; } 1;
.../dir1/main.pl
#!/usr/bin/perl # pl -> exe: # pp -a "../lib/TM/TestModuleMath.pm" -o TestMain.exe TestMain.pl # -M Data::Dumper use strict; use warnings; use File::Basename qw(dirname); use Cwd qw(abs_path); use lib dirname(dirname abs_path $0) . '/lib'; # => <ThisScript>../lib use TM::TestModuleMath qw(add); print add(19, $var);
Crypto: Hashing
use MIME::Base64; # encode_base64,decode_base64 # Base64 = 6 Bit encoded: A-Z,a-z,0-9,+,/ see https://en.wikipedia.org/wiki/Base64#Base64_table use Digest::MD5 qw(md5 md5_hex md5_base64); use Digest::SHA qw(sha512 sha512_hex sha512_base64); my $data = "Moin Torben"; say "Data:\t'" . $data . "'"; say "Base64:\t'" . encode_base64( $data ) . "'"; say "MD5:\t'" . md5( $data ) . "'"; say "MD5hex:\t'" . md5_hex( $data ) . "'"; say "MD5base64:\t'" . fix_base64_padding( md5_base64( $data ) ) . "'"; say "SHA512:\t'" . sha512( $data ) . "'"; say "SHA512hex:\t'" . sha512_hex( $data ) . "'"; say "SHA512base64:\t'" . fix_SHA_base64_padding( sha512_base64( $data ) ) . "'"; sub fix_base64_padding { # appends "=" unless length is of modulo 4 # from https://perldoc.perl.org/Digest/SHA.html#PADDING-OF-BASE64-DIGESTS my ( $s ) = @_; while ( length( $s ) % 4 ) { $s .= '='; } return $s; }
HTTP POST/GET using LWP
my $ua = LWP::UserAgent->new(); # create User Agent using LWP my %h; # hash for http parameters $h{ 'id' } = $id; $h{ 'secret' } = $secret; $h{ 'code' } = $code; my $response = $ua->post( $url, \%h ); # or get my $cont = $response->as_string(); $cont = decode( 'UTF-8', $cont );
V0: LWP:Simple
use LWP::Simple; # network access # read a file my $content = get( $url ); getstore( $url, $localfilename ); # download file
Attention: for access to some pages like Wikipedia you need to rename the useragent, as perl lwp is on their blacklist:
use LWP::Simple qw($ua get getstore); $ua->agent( 'My agent/1.0' );
WWW::Mechanize Web Browsing
my $mech = WWW::Mechanize->new(); my $url = "https://www.google.de"; $mech->get( $url ); # print Dumper $mech->content() and die; # print Dumper $mech->forms() and die; # print Dumper $mech->form_id('login_form') and die; # Form Submit Var 1 # $mech->set_fields( email => 'myemail', password => 'mypassword' ); # $mech->submit(); # print Dumper $mech->content() and die; # Form Submit Var 2 $mech->submit_form( form_id => 'login_form', fields => { email => 'myemail', password => 'mypassword' }, ); die "Error: no success" unless ($mech->success); print Dumper $mech->content() and die;
Strava: Web access URL, download contend, decode JSON to hash
use utf8; use Encode; use LWP::UserAgent; # creat User Agent using LWP my $url = "https://www.strava.com/api/v3/athlete"; my $req = HTTP::Request->new( GET => $url ); my $token = 1234567890; $req->header( 'Accept' => 'application/json' ); $req->header( 'Accept-Encoding' => 'UTF-8' ); $req->header( 'Authorization' => "Bearer $token" ); my $ua = LWP::UserAgent->new(); $ua->agent( "MyApp/0.1 " ); my $res = $ua->request( $req ); if ( not $res->is_success ) { print "HTTP get code: ", $res->code, "\n"; print "HTTP get msg : ", $res->message, "\n"; # use Data::Dump qw/ dd /; # dd( $res->as_string ); die( "leaving" ); } my $cont = $res->decoded_content; # content, decoded if it was zipped $cont = decode( 'UTF-8', $cont ); use JSON; # imports encode_json, decode_json, to_json and from_json. my $j = JSON->new->allow_nonref; my $decoded = $j->decode( $cont ); # decoded results hashref or arrayref if ( ref( $decoded ) eq "HASH" ) { my %h = %{ $decoded }; } elsif ( ref( $decoded ) eq "ARRAY" ) { my @L = @{ $decoded }; } else { die "E: message '$decoded' is no HASHREF or ARRAYREF"; }
Templates / Snippets
Trim String
sub trimString { my $s = shift; $s =~ s/[\s\r\n]+/ /g; $s =~ s/(^ | $)//g; return $s; }
Generation of tables from file contents
#!/usr/bin/perl use strict; use warnings; use 5.010; # Perl Standard Modules use File::Basename; use Encode; my $s; my @L; my @ListOfFiles = <input/*.csv>; my $fileOut = "report.csv"; open my $fh, '>:encoding(UTF-8)', $fileOut or die "ERROR: Can't write to file '$fileOut': $!"; my @Reihenfolge = qw( Kunde Bezeichnung ); $s = join "\t", @Reihenfolge; # header line say { $fh } $s; foreach my $file ( @ListOfFiles ) { my %h; my ( $fname, $fdir, $fext ) = fileparse( $file, qr/\.[^.]*/ ); say $fname; $h{ 'Kunde' } = $kunde; $h{ 'Bezeichnung' } = $bez; @L = map { $h{ $_ } } @Reihenfolge; $s = join "\t", @L; say { $fh } $s; } close { $fh };
VCard
find end of tag:
.*?(?=\n[A-Z\-]+[:;])
Base64->JPEG
use File::Basename; use MIME::Base64; my $vcard; $vcard =~ s/\r\n/\n/g; # EOL -> Linux $vcard =~ m/\nPHOTO[^:]*:(.*?)(?=\n[A-Z\-]+[:;])/s; my $photo = $1; my ( $fname, $fdir, $fext ) = fileparse( $fileIn, qr/\.[^.]*/ ); my $photoOut = "output/$fname.jpg"; my $photoDecoded = MIME::Base64::decode_base64( $photo ); open my $fhPhoto, '>', $photoOut or die $!; binmode $fhPhoto; print { $fhPhoto } $photoDecoded; close $fhPhoto;
Debugging
Size of variable in bytes
use Devel::Size qw(total_size); say total_size(\@matrix);
Total memory usage from [12]
use Memory::Usage; my $mu = Memory::Usage->new(); # Record amount of memory used by current process $mu->record('starting work'); # Do the thing you want to measure $object->something_memory_intensive(); # Record amount in use afterwards $mu->record('after something_memory_intensive()'); # Spit out a report $mu->dump();
UnitTests
see [13] file script.pl containing the logic
sub add { my ( $a, $b ) = @_; return $a + $b; }
file script.t containing the testcases
use diagnostics; use Test::More; # qw( no_plan ); use lib( '.' ); do 'script.pl'; plan tests => 6; # set how many test cases are going to be executed, to check to unintended die() etc. # tests go here is( add( 0, 0 ), 0, 'Nullsumme' ); isnt( add( 0, 0 ), 1, 'Nullsumme - isn\'t' ); # Sometimes you just want to say that the tests have passed. Usually the case is you've got some complicated condition that is difficult to wedge into an ok() pass( "test X" ); # synonym for ok( 1, "test X" ); fail( "test Y" ); # synonym for ok( 0, "test Y" ); subtest 'some subtests with own test plan count' => sub { plan tests => 2; is( add( -1, +1 ), 0, '+/-1' ); is( add( 0.00001, 0.00009 ), 0.0001, 'float' ); }; pass( "test Z" );
run via
perl script.t
Constant Variables
variant 1 using constant pragma
use constant PI => 4 * atan2(1, 1); use constant DEBUG => 0; print "Pi equals ", PI, "...\n" if DEBUG; use constant WEEKDAYS => qw( Sunday Monday Tuesday Wednesday Thursday Friday Saturday ); print "Today is ", (WEEKDAYS)[ (localtime)[WDAY] ], ".\n";
variant 2 using Const::Fast
use Const::Fast; const my $foo => 'a scalar value'; const my @bar => qw/a list value/; const my %buz => ( a => 'hash', of => 'something' );
Parse HTML
use HTML::TreeBuilder 5 -weak; # ... my $tree = HTML::TreeBuilder->new; # $tree->parse($url); # $tree->parse_file($fileIn); $tree->parse_content($html); my $e = $tree-> look_down('class', 'infobox geography vcard'); if (defined $e) { print $e->as_HTML; # print $e->as_text; } else { die "E: 'infobox geography vcard' not found"; }
Perltidy Parameters
see Visual Studio Code, for a complete list see [14]
Threads
Parallel computation using multiple threads for usage of multiple cpu cores. From [15]
use strict; use warnings; use threads; use Thread::Queue; my $numThreads = 2; # Preparing Threads my $q = new Thread::Queue; my @Threads = (); # the work to be done for all items of thread queue sub tsub { my $thrNum = shift; # processing query while (my $x = $q->dequeue) { $_ = "$pathToGnuplot $gpoutfile"; say "Thread $thrNum: $x"; system($x); # $x = gnuplotfile } return; } # Filling the queue with the list $q->enqueue($_) for (@OutFiles); for (1..$numThreads) { $q->enqueue(undef); } # for stop condition # Fill the Threads Object for my $thrNum (1..$numThreads) { push @Threads,threads->new(\&tsub,$thrNum); } # Wait for all threads to complete execution. foreach (@Threads) { print $_->join; # wait for thread, returns return value }
Sharing variables
use threads::shared; my $count :shared ; ... sub threadSub{ lock ($count); #until end of block $count ++; # test }
Archive::Zip
use Archive::Zip qw( :ERROR_CODES :CONSTANTS );
Create zip and add stuff
my $zip = Archive::Zip->new(); # Create a Zip file foreach my $i (@list) { if (-d $i) { $zip->addTree($i,$i);} elsif (-f $i) { $zip->addFile($i);} } die unless ( $zip->writeToFileNamed($newZipFilename) == AZ_OK );
Extract a single file from zip archiv
my $zip = Archive::Zip->new(); die unless ( $zip->read( $templatefilename ) == AZ_OK ); $zip->extractMember( $fileNameToExtract );
Remove a file from zip archiv
my $zip = Archive::Zip->new(); die unless ( $zip->read( $newZipFilename ) == AZ_OK ); my $member = $zip->memberNamed( $fileToExtract ); $zip-> removeMember ($member); die unless ($zip->overwrite() == AZ_OK); # save unlink $fileToExtract; push @ODPs, $newZipFilename;