#!/usr/bin/env perl # Written by Scott Baker - https://www.perturb.org/ # Last updated: 2023-10-03 use strict; use warnings; use v5.16; use Time::HiRes qw(sleep CLOCK_MONOTONIC); use Getopt::Long; use Time::Piece; my $delay = 1; my $line_filter = undef; my $ok = GetOptions( 'delay|n=f' => \$delay, 'line=i' => \$line_filter, ); ############################################################################### ############################################################################### my @cmd = @ARGV; if (!@cmd || !$ok) { die(usage()); } while (1) { my $coderef = \&output_line; sched('timer1', $delay * 1000, $coderef); } ############################################################################### ############################################################################### sub output_line { my $t = localtime; my $date_str = $t->ymd . " " . $t->hms; my $out = `@cmd`; chomp($out); if (defined($line_filter)) { my @x = split(/\n/, $out); $out = $x[$line_filter - 1] || ""; } if (!$out) { k("Warning: no output from command"); } # If it's multiline we indent things a little bit if ($out =~ /\n/) { my $len = length($date_str) + 2; my $pad = " " x $len; $out =~ s/\n^/\n$pad/mg; print "$date_str: $out\n"; } else { print "$date_str: $out\n"; } } sub trim { my $s = shift(); if (!defined($s) || length($s) == 0) { return ""; } $s =~ s/^\s*//; $s =~ s/\s*$//; return $s; } # String format: '115', '165_bold', '10_on_140', 'reset', 'on_173', 'red', 'white_on_blue' sub color { my $str = shift(); # If we're NOT connected to a an interactive terminal don't do color if (-t STDOUT == 0) { return ''; } # No string sent in, so we just reset if (!length($str) || $str eq 'reset') { return "\e[0m"; } # Some predefined colors my %color_map = qw(red 160 blue 27 green 34 yellow 226 orange 214 purple 93 white 15 black 0); $str =~ s|([A-Za-z]+)|$color_map{$1} // $1|eg; # Get foreground/background and any commands my ($fc,$cmd) = $str =~ /^(\d{1,3})?_?(\w+)?$/g; my ($bc) = $str =~ /on_(\d{1,3})$/g; # Some predefined commands my %cmd_map = qw(bold 1 italic 3 underline 4 blink 5 inverse 7); my $cmd_num = $cmd_map{$cmd // 0}; my $ret = ''; if ($cmd_num) { $ret .= "\e[${cmd_num}m"; } if (defined($fc)) { $ret .= "\e[38;5;${fc}m"; } if (defined($bc)) { $ret .= "\e[48;5;${bc}m"; } return $ret; } sub file_get_contents { my $file = shift(); open (my $fh, "<", $file) or return undef; my $ret; while (<$fh>) { $ret .= $_; } return $ret; } sub file_put_contents { my ($file, $data) = @_; open (my $fh, ">", $file) or return undef; print $fh $data; return length($data); } sub usage { my $ret = "Usage: $0 [--delay 15] [--line 3] command\n"; $ret .= "\n"; $ret .= "# Run `my_command` every 15 seconds\n"; $ret .= "Example: $0 --delay 15 'my_command | grep stuff'\n"; $ret .= "\n"; $ret .= "# Filter output to only online line 2\n"; $ret .= "Example: $0 --line 2 'ping -c 1 192.168.5.222'\n"; return $ret; } sub monotime { return Time::HiRes::clock_gettime(CLOCK_MONOTONIC) }; sub sched { my ($name, $ms, $coderef) = @_; # Run the code my $start = monotime(); $coderef->($name); my $now = monotime(); # Next run will be $ms in the future minus the time it took to run the coderef state $nextrun; if (!$nextrun) { $nextrun = $now + ($ms / 1000) - ($now - $start); } my $sleep = $nextrun - $now; if ($sleep > 0) { Time::HiRes::sleep($sleep); $nextrun += ($ms / 1000); return 1; } else { # Reset if falling behind $nextrun = $now + ($ms / 1000); return -1; } } # Debug print variable using either Data::Dump::Color (preferred) or Data::Dumper # Creates methods k() and kd() to print, and print & die respectively BEGIN { if (eval { require Data::Dump::Color }) { *k = sub { Data::Dump::Color::dd(@_) }; } else { require Data::Dumper; *k = sub { print Data::Dumper::Dumper(\@_) }; } sub kd { k(@_); printf("Died at %2\$s line #%3\$s\n",caller()); exit(15); } } # vim: tabstop=4 shiftwidth=4 autoindent softtabstop=4