Author image thrig
and 1 contributors

NAME

Unix::ScriptUtil - some utility routines for scripts

SYNOPSIS

  use Unix::ScriptUtil
    qw(cd brun diropen fileopen
       pipe_close pipe_from pipe_to
       solitary timeout);

  cd '/some/dir';           # or croaks
  brun qw[some command];    # or croaks
  my $dh = diropen '.';     # or croaks
  my $fh = fileopen 'file'; # or croaks
  my $pf = pipe_from qw[some command]; # or croaks (maybe)
  my $pt = pipe_to   qw[some command]; # or croaks (maybe)

  solitary '/some/dir', qw[some command]; # or croaks (maybe)

  timeout 3, sub { brun qw[sleep 5] }; # croaks (or should)

DESCRIPTION

Unix::ScriptUtil contains various utility functions to assist with scripts; these may optionally be exported into the caller for easy use. The functions tend to "do it or die" (in truth mostly croak) to cut down on boilerplate error handling.

Some effort is taken to avoid having calls route through the shell though the pipe_from and pipe_to may call sh under certain circumstances.

FUNCTIONS

cd dir

Like chdir but calls croak if that fails.

brun command, args ..

Like system but calls croak if the exit status word is not 0.

diropen directory

Like opendir but dies if the directory cannot be opened. Returns a directory handle.

fileopen file, mode

Returns a filehandle to the file or calls croak if something went awry. mode is based on fopen(3) and r is the default, so

  fileopen($file, 'r'); # default, is like open(..., '<'
  fileopen($file, 'w'); # is like '>'
  fileopen($file, 'a'); # is like '>>'

and so forth are supported; see the source code for the exact list. r+ is probably better than w+ unless you do want to clobber the file first.

pipe_close fh

Wrapper around close that checks $! or $? for where an error may be hiding, and calls croak if there is one.

pipe_from command, args ..

Returns a filehandle (or croaks) that can be used to read output from the command. Note that some error conditions will only be available when the filehandle is closed, consider

  # fails on pipe_to()
  $fh = pipe_to "nosuchcommand.$$";

  # fails on close()
  $fh = pipe_to 'false';
  pipe_close $fh;
pipe_to command, args ..

As previous only returns a filehandle that can be used to print output to the input of the command. Use something else if you need a bi-directional pipe.

solitary directory, command, args ..

Forks the given command, running it in the given directory with a new session (via setsid(2)) and STDIN, STDOUT, and STDERR reopened to /dev/null. Will croak on various errors though various failure may be hidden behind the double-fork and output reopens.

timeout duration, coderef

A small wrapper around alarm. Runs the given coderef but throws an error if the code takes longer than duration to run. Will die if there is a non-timeout error. The coderef should not directly or indirectly mess around with the ALRM signal handler.

ENCODING

No particular encoding is assumed or enforced by this module. If necessary use binmode on the filehandles or the open module to set the desired encoding.

WHY NOT JUST WRITE A SHELL SCRIPT

tl;dr if you catch yourself doing something complicated in a shell script (like using logic, looping over data, etc) it's probably time to use some other language.

  $ which bash
  which: bash: Command not found.

Silent Data Loss

  $ (echo data; echo -n loss) | while read line; do echo $line; done
  data
  $ 

there is the less buggy

  while IFS= read line || [ -n "$line" ]; do ...

but that is a verbose and slow way to say in Perl

  while (my $line = readline) { ...

Crouching TRS-80 Hidden Child Process

The shell will sometimes run code in some other process, so something simple like setting a variable to use later

  $ x=FALSE; echo foo | while read line; do x=TRUE; done; echo $x         
  FALSE

confounds you (a prime motivator for this module).

POSIX Word Split Glob

The shell command

  foocmd $arg

may be totally unsafe due to the invisible POSIX split and then glob on $arg that is actually something like the following in Perl

  system 'foocmd', map { glob } split /[\n \t]/, $arg;

especially if a file path has a space in the name and rm -rf is the command.

A safer shell command might disable options processing, quote the variable to turn off the word split glob fun, and could check for errors,

  foocmd -- "$arg" "$and" "$another" || exit 1

which in this module could be written as

  brun qw[foocmd --], $arg, $and, $another;

or better yet

  brun qw[foocmd --], @args;

BUGS

Patches might best be applied towards:

https://github.com/thrig/Unix-ScriptUtil

SEE ALSO

autodie is another way to make calls always blow up.

fileopen is probably very similar to File::Open. See also File::Slurper for routines to get file contents into and out of perl data structures. Also File::AtomicWrite if you are worried about the filesystem corrupting the data or want more atomicity when adjusting the contents of files.

There exist various other IPC and system modules that may better suit your needs, e.g. IPC::Run, IPC::System::Simple, etc. Capture::Tiny can be used to collect or hide output from programs.

Something like Parallel::PreFork may be necessary if you want to keep tabs on child processes, unlike the fire and forget solitary.

Import::Into or Import::Base can cut down on script boilerplate use statements.

AUTHOR

thrig - Jeremy Mates (cpan:JMATES) <jmates at cpan.org>

COPYRIGHT AND LICENSE

Copyright 2019 Jeremy Mates

This program is distributed under the (Revised) BSD License: https://opensource.org/licenses/BSD-3-Clause