Helper functions: Perl language
|
Background
I prefer to use only Perl’s core modules (or those of any other language I’m using) for portability. Thus I’m always writing some of the functionalities that I need and use. |
|
Version
I tested and used these code against this version of Perl:
Output
This is perl 5, version 38, subversion 2 (v5.38.2) built for x86_64-linux-gnu-thread-multi (with 51 registered patches, see perl -V for more detail) Copyright 1987-2023, Larry Wall Perl may be copied only under the terms of either the Artistic License or the GNU General Public License, which may be found in the Perl 5 source kit. Complete documentation for Perl, including FAQ lists, should be found on this system using "man perl" or "perldoc perl". If you have access to the Internet, point your browser at https://www.perl.org/, the Perl Home Page. |
And there we go.
1. Logging
Better terminal output.
Modules
use warnings;
use strict;
use POSIX qw(strftime); # current time
Subroutine
sub logger {
my $subname = (caller(0))[3];
my ($package, $filename, $line) = caller();
my ($msg, $level, $modname) = @_;
my $message;
$modname = $subname unless $modname;
my $current_date = strftime("%Y-%m-%d %H:%M:%S", localtime);
$message = "] $current_date ${filename} ${modname} ${line} | $msg";
if ($level eq 1) {
print("$message\n");
} elsif ($level eq 2) {
print(STDERR "$message\n");
} elsif ($level eq 3) {
print(STDERR "$message\n");
exit $line;
}
}
How to use it
unless (-d $path) {
logger("$path: No such file or directory", 3, $subname);
}
Output
] 2025-09-04 15:36:07 navigator main::finder 46 | /dev/null: No such file or directory
2. Finding files
Find files or directories basted on path, type and name pattern
Modules
use Cwd 'abs_path'; # get absolute path
Subroutine
sub finder {
my $subname = (caller(0))[3];
my ($opts) = @_; # hashref
$opts ||= {};
my $path = $opts->{path} // '.';
my $type = $opts->{type} // '';
my $pattern = $opts->{pattern} // '';
my $hidden = $opts->{hidden} // 0;
unless (-d $path) {
logger("$path: No such file or directory", 3, $subname);
}
$path =~ s,/$,,; # remove trailing /
my @directories;
opendir (my $dir, $path);
while (my $entry = readdir $dir) {
my $full_path;
if ($path =~ /^\//) { # double check for full name vs relative name
$full_path = $path."/".$entry;
} else {
$full_path = abs_path($entry);
}
unless ($hidden) {
next if $entry =~ /^\./; # show hidden files or don't
}
next if $entry eq '.' or $entry eq '..'; # ignore . and .. directory
if ($type eq "d") {
next unless -d $full_path;
} elsif ($type eq "f") {
next unless -f $full_path;
} elsif ($type eq "l") {
next unless -l $full_path;
} else {
next unless -e $full_path;
}
if ($pattern) {
if ($entry =~ /$pattern/) {
push(@directories, $full_path);
}
} else {
push(@directories, $full_path);
}
}
return sort @directories;
closedir $dir;
}
How to use it
my @root = finder({path => "/tmp", type => "d", pattern => "systemd"});
foreach my $dir (@root) {
print("$dir\n");
}
Output
/tmp/systemd-private-8d727da391f74241890c032844d152d1-ModemManager.service-NByLAH /tmp/systemd-private-8d727da391f74241890c032844d152d1-fwupd.service-A4BZql /tmp/systemd-private-8d727da391f74241890c032844d152d1-polkit.service-lYqcFr /tmp/systemd-private-8d727da391f74241890c032844d152d1-power-profiles-daemon.service-Fx9E2m /tmp/systemd-private-8d727da391f74241890c032844d152d1-switcheroo-control.service-J56YmG /tmp/systemd-private-8d727da391f74241890c032844d152d1-systemd-logind.service-qi68SJ /tmp/systemd-private-8d727da391f74241890c032844d152d1-systemd-resolved.service-bN81cy /tmp/systemd-private-8d727da391f74241890c032844d152d1-systemd-timesyncd.service-T6AXjP /tmp/systemd-private-8d727da391f74241890c032844d152d1-upower.service-nTuKst
3. Fetch / Download file
Yeah, I don’t know why, But I wrote it anyway :)
Modules
use File::Path qw(make_path); # directory handler
use File::Fetch; # remote handler
use File::Basename; # filename handling
Subroutine
sub fetcher {
my $subname = (caller(0))[3];
my ($opts) = @_; # hashref
$opts ||= {};
my $url = $opts->{url} // '';
my $name = $opts->{name} // '';
my $directory = $opts->{directory} // $ENV{PWD}; # fallback to PWD
make_path($directory) unless -d $directory; # create output directory
my $ff = File::Fetch->new(uri => $url);
my $file = basename($url);
my $full_name = $directory.'/'.$ff->file;
my $return_value = {
name => $name,
url => $url,
file => $ff->file,
full_name => $full_name,
};
if (-f $full_name) {
print(STDERR "[ignore] file already exists ");
return $return_value;
}
# fetch
my $where = $ff->fetch(to => "$directory/$name") or logger($ff->error, 3);
return $return_value;
}
How to use it
fetcher({
url => "https://musl.libc.org/releases/musl-1.2.5.tar.gz",
name => "musl.tar.gz",
directory => "/tmp/fetch"
});
Output
This one is silent
ls -lhtr /tmp/fetch
total 4.0K drwxrwxr-x 2 hos hos 4.0K Sep 4 16:12 musl.tar.gz
It will complain on error only:
fetcher({
url => "https://musl.libc.org/releases=musl-1.2.5.tar.gz", # invalid URL
name => "musl.tar.gz",
directory => "/tmp/fetch"
});
Fetch failed! HTTP response: 404 Not Found [404 Not Found] at tmp.pl line 54. Command failed: at tmp.pl line 54. Command failed: at tmp.pl line 54. ] 2025-09-04 16:15:46 tmp.pl main::logger 54 | Command failed:
4. Look for a binary in the PATH
Kinda like bash’s command command =)
Modules
Uses nothing special
Subroutine
sub command {
my $program = $_[0];
my $path = $ENV{"PATH"};
my @directories = split(":", $path);
my $found = '';
foreach my $dir (@directories) {
my $full_path = "$dir/$program";
if (-x $full_path) {
$found = $full_path;
last;
}
}
return $found;
}
How to use it
if (command("magick")) {
$found_im = 0;
$runner = command("magick");
} else {
$found_im = 1;
}
Output
Does not print anything by itself
5. Shell commands
A better way to execute shell command and capture the Exit code and create an array of their output. This way we can iterate over the output line by line.
Modules
Nothing extra; just the warnings and strict.
Subroutine
sub commander {
my $subname = (caller(0))[3];
my ($package, $filename, $line) = caller();
my @commands = @_;
my $msg = "] $subname $line @commands";
my $_command = "@commands";
my $_cmd_output = `$_command`;
my $err_msg = $!;
my $cmd_exit_code = $? >> 8;
chomp($_cmd_output);
my @cmd_output = split("\n", $_cmd_output);
print(STDERR "$msg: error") if $cmd_exit_code;
if ($err_msg) {
print(": $err_msg\n");
} else {
print("\n");
}
return (\@cmd_output, $cmd_exit_code);
}
How to use it
my ($ref_cmd_output, $exit_code) = commander("ls -l /tmp");
exit 1 if $cmd_exit_code; # exit on error
my @cmd_output = @{$ref_cmd_output};
foreach my $line (@cmd_output) {
print("$line\n"); # do whatever you want with it
}
Output
total 76 drwx------ 2 hos hos 4096 Sep 3 08:17 babel-O0fn1v drwxrwxr-x 2 hos hos 4096 Sep 3 08:17 babel-stable-556 drwxrwxr-x 3 hos hos 4096 Sep 4 16:10 dl drwxrwxr-x 3 hos hos 4096 Sep 4 16:12 fetch drwxr-xr-x 2 hos hos 4096 Sep 4 16:23 hsperfdata_hos drwxr-xr-x 2 root root 4096 Sep 3 11:57 hsperfdata_root drwx------ 2 hos hos 4096 Sep 3 08:17 net-export drwx------ 4 hos hos 4096 Sep 4 16:13 nvim.hos -rw------- 1 hos hos 168 Sep 3 08:17 serverauth.JAVaP1nh0z drwx------ 3 root root 4096 Sep 2 17:57 systemd-private-8d727da391f74241890c032844d152d1-fwupd.service-A4BZql drwx------ 3 root root 4096 Sep 2 17:03 systemd-private-8d727da391f74241890c032844d152d1-ModemManager.service-NByLAH drwx------ 3 root root 4096 Sep 2 17:03 systemd-private-8d727da391f74241890c032844d152d1-polkit.service-lYqcFr drwx------ 3 root root 4096 Sep 2 17:03 systemd-private-8d727da391f74241890c032844d152d1-power-profiles-daemon.service-Fx9E2m drwx------ 3 root root 4096 Sep 2 17:03 systemd-private-8d727da391f74241890c032844d152d1-switcheroo-control.service-J56YmG drwx------ 3 root root 4096 Sep 2 17:03 systemd-private-8d727da391f74241890c032844d152d1-systemd-logind.service-qi68SJ drwx------ 3 root root 4096 Sep 2 17:03 systemd-private-8d727da391f74241890c032844d152d1-systemd-resolved.service-bN81cy drwx------ 3 root root 4096 Sep 2 17:03 systemd-private-8d727da391f74241890c032844d152d1-systemd-timesyncd.service-T6AXjP drwx------ 3 root root 4096 Sep 2 17:57 systemd-private-8d727da391f74241890c032844d152d1-upower.service-nTuKst drwx------ 2 hos hos 4096 Sep 2 18:01 tmux-1000