Pb - a workflow system made from Perl and bash


This document describes version 0.02 of Pb.


    use Pb;
    use Path::Tiny;
    use Types::Standard -types;

    my %HOSTS =
        integration => '',
        staging     => '',
        production  => '',

    command 'push-file' =>
        arg env  => one_of [ keys %HOSTS ],
        arg file => must_be Str,
        verify { pwd eq $ENV{MY_ROOT_DIR} } 'must be run from $MY_ROOT_DIR';
        verify { -f $FLOW->{file}         } 'file must exist';

        my $host = $HOSTS{$FLOW->{env}};
        my $from = path($FLOW->{file});
        my $dir  = $from->dirname;
        SH scp => -p => $from, "$host:$dir";
        CODE sub { say "it worked!" };



If you are a devops developer--even if you're a devops developer who doesn't believe that devops is really a thing--you spend a bunch of your time writing control scripts. Some of them you probably write in bash, because job control is one of those things that bash is actually good at. A lot of them you probably write in Perl, because programming is one of those things that bash is pretty terrible at. Trying to do real programming in bash is pretty horrifying, but trying to do job control in Perl isn't much nicer: the return value of system is backwards, there's no equivalent to bash -e, END blocks don't get run if your script is dying due to a signal, and so on and so forth, ad inifinitum. None of the deficiencies of Perl in this area are very significant on their own, but it can become death by a thousand cuts as you try to layer more and more devops complexity onto your Perl scripts. In the end, if you write it in bash, you'll end up wishing you'd written it in Perl, and if you write it in Perl, you'll say to yourselt at least once before it's done: man, this part would have been easier in bash.

Well, now you no longer need to choose. Leadpipe takes Perl and bash and glues them together to form Pb, a module which allows you to quickly write commands which can do one or all of the following:

  • Can have subcommands (reminiscent of git).

  • Can have command-line options and command-line arguments with sophisticated validation.

  • Can run pre-command verification checks to ensure a known state.

  • Can break your command down into steps (called "directives"), where each step can be printed out instead of performing it (in --pretend mode) or performed only after verification from the user (in --interactive mode).

  • Shell (SH) directives look (mostly) just like bash commands, with quoting handled for you easily, and you have the full range of bash syntax (unlike with system).

  • But you can also do code (CODE) directives, which are pure Perl.

  • And run (RUN) directives, which allow one subcommand to call another, retaining the environment and context.

  • Will abort if any individual directive fails (similar to bash -e).

  • Can automatically log to a file.

  • Can automatically abort if a previous instance of the script is running.

  • Can save the result of the script run to a file, and (optionally) refuse to run if the previous run failed. This can be useful in a cronjob situation where a run error needs to be cleared manually before automated runs can resume.

  • Much much more.

Honestly, most of the things discussed above are not original to this module. The main value of Leadpipe is that it glues it all together and gives you a declarative syntax that looks like Perl (because it is), but also looks like what you're actually doing. Syntax is provided in a similar fashion to Moose (i.e. not using source filters or Devel::Declare or even Perl's new(ish) keyword plugin API), but Moo is used rather than Moose to keep startup time quick.

The main functionality of Leadpipe is provided by the following modules:

If you decide not to use Leadpipe (perhaps you don't like the glitzy syntax layer), you should definitely look at those other modules for whatever solution you're contemplating.



Declare a Pb command.


Declare a base (i.e. default) command.


Declare an argument to a command.


Declare an option to a command.


Specify the type (of either an argument or option).


Specify the valid values for an enum type (for either an argument or option). Use instead of must_be, not in addition to.


Specify additional properties (other than type) for an option.


Specify a logfile for the output of a command.


Specify a control structure. This is where you put pidfile, statusfile, etc.


Specify the code for the actual command.


Make an assertion (using a code block) which must return a true value before the command will execute. Also specify the error message if the assertion fails.


Run a command in bash. If the command does not exit with 0, the entire command will exit.


Run a code block. If the block returns a falsey value, the entire command will exit.


Run one command inside another. Although you pass the nested command its own arguments, all other parts of the context (including options) are retained.


Print a fatal error and exit.


This module is very new, although it is being actively used for small projects. Documentation is skeletal and the API may change a bit before it all shakes out. Please be cautious in adopting it for anything mission-critical, but I would love to get any feedback you may have from trying it out for your own experimental projects.


Doing use Pb gets you all the syntax above, plus the following global(ish) variables:


This is the Pb::Command::Context for the currently running command. Context variables (including arguments) can be accessed as if $FLOW were a hashref (e.g. $FLOW->{file}), but it's really an object with proper methods (e.g. $FLOW->error).


This is the hash which contains the values of all options to the command (e.g. $OPT{pretend}).

You also get these functions passed through from PerlX::bash:


Same as "cwd" in Cwd.


Probably lots. Check out in the distribution for things that are in the queue to be done.



You can find documentation for this module with the perldoc command.

  perldoc Pb

Bugs / Feature Requests

This module is on GitHub. Feel free to fork and submit patches. Please note that I develop via TDD (Test-Driven Development), so a patch that includes a failing test is much more likely to get accepted (or at least likely to get accepted more quickly).

If you just want to report a problem or suggest a feature, that's okay too. You can create an issue on GitHub here:

Source Code


  git clone


Buddy Burden <>


This software is Copyright (c) 2020 by Buddy Burden.

This is free software, licensed under:

  The Artistic License 2.0 (GPL Compatible)