Scriptalicious(3pm)

πŸ“¦
version 1.17 Β· 5 versions
, in ubuntu noble, ubuntu resolute, ubuntu stonking

🌸 Scriptalicious 🌸

okay bestie, this is the manual page for Scriptalicious(3pm) from the User Contributed Perl Documentation section. ✨ heads up: this page is a whole saga, so this is part 1 of 2 β€” more is coming, don't go anywhere. πŸŽ€


πŸ’– NAME πŸ’–

Scriptalicious β€” Make scripts more delicious to SysAdmins


πŸ¦‹ SYNOPSIS πŸ¦‹

 use Scriptalicious
      -progname => "pu";

 our $VERSION = "1.00";

 my $url = ".";
 getopt getconf("u|url" => \$url);

 run("echo", "doing something with $url");
 my $output = capture("svn", "info", $url);

 __END__

 =head1 NAME

 pu - an uncarved block of wood

 =head1 SYNOPSIS

 pu [options] arguments

 =head1 DESCRIPTION

 This script's function is to be a blank example that many
 great and simple scripts may be built upon.

 Remember, you cannot carve rotten wood.

 =head1 COMMAND LINE OPTIONS

 =over

 =item B<-h, --help>

 Display a program usage screen and exit.

 =item B<-V, --version>

 Display program version and exit.

 =item B<-v, --verbose>

 Verbose command execution, displaying things like the
 commands run, their output, etc.

 =item B<-q, --quiet>

 Suppress all normal program output; only display errors and
 warnings.

 =item B<-d, --debug>

 Display output to help someone debug this script, not the
 process going on.

 =back

πŸ‘‘ DESCRIPTION πŸ‘‘

okay so, bestie. this module? it literally exists to help you write scripts that conform to best common practices, and to do it fast. πŸƒβ€β™€οΈπŸ’¨

just include all of the above as a template, and your script will accept every single one of the options that are in the manual page β€” and it'll summarise them for you whenever someone hits -h. iconic.

(sadly it's not possible yet to have a use dependency automatically add structure to your POD, so you have to paste the above in manually. that is, if you want your help message and your Perldoc to actually be meaningful. 😭)

the module also hands you shortcuts for bailing out or dying with a bunch of different error conditions. and every single one of them prints the name of the program that's running (pulled from $0 if you didn't pass it in). πŸ’…

why tho? because "small" scripts tend to just get written and then forgotten about, fr fr. so when you've got a big system that's built out of a whole pile of these little pieces, figuring out which script a printed message came from is straight-up guesswork. πŸ™ˆ

so like β€” if your program gets called with invalid arguments, you can just call abort with a one-line message saying what the specific problem was. when the script actually runs, it invites the person running it to try the --help option, which gives them a summary, which in turn invites them to read the Perldoc. it reads so nicely in the source:

  @ARGV and abort "unexpected arguments: @ARGV";
  $file or abort "no filename supplied";

and in the output:

  somescript: no filename supplied!
  Try `somescript --help' for a summary of options

on the other hand, if you call barf, that's considered a hard run-time failure, and there is no invitation to go read the --help page for usage. also the messages are way tidier than the ones you get from die and friends. ✨

  open FOO, "<$file" or barf "failed to open $file; $!";

which will print:

  somescript: failed to open somefile; Permission denied

Scriptalicious has no hard dependencies. 🫢 every last method β€” except for reading passwords from the user β€” will work with zero extra modules installed, on every version of Perl from 5.6.0 onwards.

and to save you from having to explicitly import symbols all over the place, these symbols and functions get exported into the caller's namespace:

🌈 $VERBOSE

set to 0 by default, and 1 if -v or --verbose was found during the call to getopt(). extra -v's or --debug will push this variable higher. and if -q or --quiet is specified, this ends up being less than one. πŸ‘€

🌈 $PROGNAME

recommended usage: only ever read this variable, and pass it in via the import. it is not automatically extracted from the POD, for performance reasons.

🌈 $CONFIG

the name of the configuration file. set it before a call to getconf, or read it afterwards to see whether a config file was actually used. New in Scriptalicious 1.16 πŸŽ‰

🌈 getopt(@getopt_args)

fetches arguments via Getopt::Long::GetOptions. the bundling option is enabled by default β€” which is different from the standard configuration of Getopt::Long. to change the configuration, just call Getopt::Long::config. see Getopt::Long for more.

🌈 getopt_lenient(@getopt_args)

exactly like getopt(), except it does not fatal-error when there are unknown options. chill mode activated. 😌

🌈 getconf(@getopt_args)

fetches configuration. takes arguments in the same form as getopt().

the configuration file is expected to be at ~/.PROGNAMErc, /etc/perl/PROGNAME.conf, or /etc/PROGNAME.conf. only the first file found gets read, and unknown options are ignored, for the time being.

the file is expected to be in YAML format, with the top entity being a hash, and the keys of that hash being the same as specifying options on the command line. using YAML as the format allows some simplifications to getopt-style processing β€” =s% and =s@ style options are expected to be in a real hash or list format in the config file, and boolean options have to be set to true or false (or some common equivalents).

returns the configuration file as Load()'ed by YAML in scalar context, or the argument list it was passed in list context.

for example, this minimal script (missing the documentation, but hey β€” at least it's a start):

  getopt getconf
      ( "something|s" => \$foo,
        "invertable|I!" => \$invertable,
        "integer|i=i" => \$bar,
        "string|s=s" => \$cheese,
        "list|l=s@" => \@list,
        "hash|H=s%" => \%hash, );

will accept all of these invocation styles:

  foo.pl --help
  foo.pl --version
  foo.pl --verbose
  foo.pl --debug
  foo.pl --quiet

  foo.pl --something
  foo.pl --invertable
  foo.pl --no-invertable    <=== FORM DIFFERS IN CONFIG FILE
  foo.pl --integer=7
  foo.pl --string=anything
  foo.pl --list one --list two --list three
  foo.pl --hash foo=bar --hash baz=cheese

and these are the equivalent config files:

  something: 1

  invertable: on

  invertable: off

  integer: 7

  string: anything

  list:
    - one
    - two
    - three

  list: [ one, two, three ]

  hash:
    foo: bar
    baz: cheese

ngl, more complex and possibly arcane usages of Getopt::Long features may not work with getconf β€” (patches welcome). πŸ™

this can be super handy for stuff like database connection strings. all you do is make an option for them, and then the user of the script, or a SysAdmin, can set up their own config file and the script automatically picks those options up. main character energy honestly. πŸ’…

🌈 getconf_f($filename, @getopt_args)

same as getconf(), but you get to specify the filename. πŸŽ€

🌈 say "something"

prints a message to standard output β€” unless quiet mode (-q or --quiet) was specified. this one's for normal program messages.

🌈 mutter "progress"

prints a message to standard output, but only if verbose mode (-v) or debug mode (-d) is enabled (that is, if $VERBOSE > 0). these are the messages designed to help someone using the script see more about what's going on.

🌈 whisper "detail"

prints a message to standard output, but only if debug mode (-d) is enabled or if multiple verbose options were passed (that is, if $VERBOSE > 1). these are messages designed to help someone debugging the script see more about what's going on internally to the script. πŸ•΅οΈβ€β™€οΈ

🌈 abort "won't go to sea in a storm"

prints a short program usage message (extracted from the POD synopsis) and exits with an error code.

🌈 moan "weather is miserable"

prints a warning to standard error. it's preceded with the text warning:. the program does not exit.

🌈 protest "don't know the weather report"

prints an error message to standard error. it's preceded with the text error:. the program does not exit.

🌈 barf "hit an iceberg"

prints a warning to standard error. it's preceded with the text warning:. the program does not exit.

real talk: that is exactly what this entry says in the list, bestie β€” even though the big paragraph further up calls barf a hard run-time failure that doesn't hand out the --help invite. πŸ’€ i'm not fixing it, i'm not hiding it, i'm just handing you the page as-is so nothing gets lost.

🌈 run("command", "arg1", "arg2")

runs a command or a closure, and barfs with a relevant error message if there's a problem. program output is suppressed unless you're running in verbose mode.

run() and the three alternatives listed below can do arbitrary filehandle redirection before executing the command. this is a genuinely convenient way to do shell-like filehandle plumbing. πŸͺ 

for example:

  run( -in => sub { print "Hello, world!\n" },
       -out => "/tmp/outfile",
       -out2 => "/dev/null",
       @command );

this connects the child process' standard input (-in) to a closure that's printing "Hello, world!\n". the output from the closure shows up on the standard input of the run command. note that the closure runs in a sub-process, so it will not be able to set variables in the calling program. πŸ™ˆ

it also connects the program's standard output (-out) to /tmp/outfile, and its standard error (filehandle 2) to /dev/null β€” the traditional way of junking error output.

if you wanted to connect two filehandles to the same place, you can pass in GLOB references instead:

  run( -out => \*MYOUT,
       -out2 => \*MYOUT,
       @command );

any filehandle can be opened in any mode. -in merely defaults to meaning -in0, and -out defaults to meaning -out1. there is no -err β€” use -out2. -rw does exist (defaulting to -rw0), but it's probably of limited use.

here's an example of using prompt_passwd() to hijack gpg's password grabbing:

  my $password = prompt_passwd("Encryption password: ");
  my $encrypted = run( -in4 => sub { print "$password\n" },
                       "gpg", "--passphrase-fd", "4", "-c", $file )

🌈 run_err("command", "arg2", "arg1")

same as run, but it returns the error code instead of assuming the command is going to complete successfully. and again β€” output is suppressed.

🌈 capture("command", "1gra", "2gra")

runs a command, captures its output, and barfs if there's a problem. returns the output of the command as a list or as a scalar.

🌈 capture_err("command", "foo")

works like capture, except the first returned item is the error code of the command ($?) rather than the first line of its output. also, it only ever returns the output as a list.

usage:

my ($rc, @output) = capture_err("somecommand", @args);

That's how you'd use the tail of the previous one, just so it doesn't feel abandoned πŸ’–

πŸ’… hush_exec() / unhush_exec()

okay so hush_exec is the move when the programs you're running are only interesting to, like, someone actively debugging your script. basically it tells Scriptalicious shhh 🀫 β€” so the messages that show which commands got run, plus the execution timings, will not be printed unless you pass -vv (double verbose) or -d (debug, which is literally the same thing). unhush_exec() undoes it, obvs.

⏱️ start_timer() / show_delta() / show_elapsed()

these three little functions are for printing run times in your scripts πŸ’«. normally, times get displayed for running external programs when you're in verbose mode β€” but these let you display running times for your main program easily. no cap, super handy.

πŸ”¬ sci_unit($num, [$unit, $precision])

returns a number, scaled using normal scientific prefixes (running all the way from atto to exa ✨). you can optionally specify a precision, which gets handed to sprintf() (peep "sprintf" in perldoc for the deets). the default is three significant figures.

as of Scriptalicious 1.08, the u character is used in place of the Greek "mu" β€” this is purely because of encoding compatibility issues, not a vibe choice πŸ™ˆ.

πŸ•°οΈ time_unit($num, [$precision])

converts a floating point number of seconds into a human-readable time 🫢. the precision sets how many significant decimal digits you get, which is used to compute a "quanta" for the value you gave β€” values below that quanta just don't get displayed. $precision defaults to 4.

examples, bestie:

time_unit(10.1) => "10.10s"
time_unit(1) => "1.000s"
time_unit(0.1) => "100ms"
time_unit(86401,2) => "1d 0h"
time_unit(86401,3) => "1d 0h"
time_unit(86401) => "1d 0h:0m"
time_unit(86400+3600/2) => "1d 0h:30m"
time_unit(86401,5) => "1d 0h:0m:1s"
time_unit(7*86400) => "1w0d 0h"
time_unit(-0.0002) => "-200us"

πŸŽ€ prompt_regex($prompt, qr/(.*)/)

prompts for something using the prompt "$prompt", then matches the value they typed (sans trailing linefeed) against the regex you passed πŸ“.

heads up: this is different from the previous behaviour of prompt_regex, which took a sub. so if you're carrying old code around, that's why it's acting up πŸ’€

πŸͺ„ prompt_sub($prompt, sub { /(.*)/ && $1 })

prompts for something using the prompt "$prompt", then feeds the entered value (sans trailing linefeed) to the sub. want a default? the sub you pass should just return it when the input comes in empty 🫑.

πŸ” prompt_passwd([$prompt])

same energy as "prompt_regex", except it turns echo off so nobody's reading your secrets over your shoulder πŸ™ˆ. $prompt defaults to ""Password: "" for this one.

✏️ prompt_string([$prompt])

prompt for a string. that's it, that's the function πŸ’…

πŸ”’ prompt_int([$prompt])

get an integer πŸ™Œ

🧁 prompt_for([ [$type =] $what])>

prompts for a value for $what. it builds a prompt that says something like ""Enter value for $what"", and then calls the equivalent "prompt_foo()" method. iykyk 🌸

βœ… prompt_yn([$prompt])

prompts for yes or no, presuming neither.

🌟 prompt_Yn([$prompt]) / prompt_yN([$prompt])

prompts for yes or no β€” presuming yes and no, respectively.

you can also spell these as prompt_nY and prompt_Ny if that's more your vibe πŸ¦‹

πŸ“¦ anydump($ref)

dumps $ref via "YAML", and falls back to "Data::Dumper" if YAML fails to load (or straight-up dies during the dump 😭). returns a string, babes.

πŸ—£οΈ tsay($template, $vars)

prints the template $template with $vars. here's the cute part β€” $template can live at the end of your file as a data section, like this:

use Scriptalicious;

tsay hello => { name => "Bernie" };

__END__

__hello__
Hello, [% name %]
[% INCLUDE yomomma %]
__yomomma__
Yo momma's so fat your family portrait has stretchmarks.

and that will print:

Hello, Bernie
Yo momma's so fat your family portrait has stretchmarks.

now, the iconic bit: the script goes to lengths to make sure your info always gets printed β€” whether or not Template Toolkit is installed πŸ‘‘. yeah it gets pretty verbose about it, but at least it solves the whole "argh! that script failed, but I don't know why because it needed this huge dependency to tell me" problem. iconic behaviour honestly.

for example, the above would be printed as:

scriptname:

🧚 foo()

if you've got a short little Perl function that does something useful for people writing Shell scripts in Perl, then please feel free to contribute it πŸ’Œ. and if it really is scriptalicious, you can bet your momma on it getting into this module! πŸŽ€


πŸ‘€ SEE ALSO

  • Simon Cozen's Getopt::Auto module does a very similar thing to this module, just in a quite different way. however, it's missing "say", "run", etc. β€” so you'll need some other module for those. that said, it does have some other features you might like, and it's probably engineered better. respect where it's due πŸ’œ
  • there's a template script at "Documentation and help texts" in Getopt::Long that contains a script template demonstrating what's necessary to get the basic man page / usage things working with the traditional Getopt::Long and Pod::Usage combination 🌸
  • Getopt::Plus is a swiss army chainsaw of Getopt::* style modules β€” contrasting with this module's approach of elegant simplicity (quiet in the cheap seats!) πŸ’…
  • Getopt::EUCLID is Damian Conway's take on this ✨
  • finally, if you don't mind the dependencies of Moose (or Mouse), then MooseX::Getopt and MooseX::SimpleConfig are much more elegant approaches to getopt handling and configuration than this module πŸ¦‹
  • if you've solved this problem in a new and interesting way, or even rehashed it in an old, boring and inelegant way and want your module listed here, please contact the

πŸ’– AUTHOR AND LICENSE πŸ’–

Sam Vilain, samv@cpan.org

Copyright 2005-2008, Sam Vilain. All rights reserved. This program is free software; you can use it and/or distribute it under the same terms as Perl itself β€” either the latest stable release of Perl when the module was written, or any subsequent stable release. πŸ“œ

please note that this applies retrospectively to all Scriptalicious releases; apologies for the lack of an explicit license πŸ™ˆ


and that's a wrap, bestie 🎬✨ this beauty lives in Scriptalicious(3pm), Perl version perl v5.36.0, dated 2022-10-16 β€” go forth and scriptaliciously slay πŸ‘‘πŸ’…πŸŒˆ

πŸ“¦ where this page comes from