Day 6 – Running External Programs from Perl 6

There are several ways to run an external program in Perl 6, let’s take a look at each way in turn.

shell

Shell is used to pass an expression to the system shell. The shell function invokes the system shell and passes a string argument to it. The return value of `shell` is the exit code of the program:

    my $exit_code = shell 'echo "Hello, World!"';
    say $exit_code; # 0 == success

If you want redirects, pipes, globs and other shell features to work in your command lines, this is the way to go.

run

Run is used to execute a program. The run function accepts a stringified program name and optional list of arguments to pass to the external program. Like shell, the return value is the exit code of the program:

    my $exit_code = run 'echo', 'Hello, World!';
    say $exit_code; # 0 == success

Capturing Output

What if you wanted to capture the output of a command, rather than just know if the command was executed successfully or not? Just use a quoting operator “q” and “qq” with the “:x” adverb to execute an expression:

    my $message = q:x/echo "Hello, World!"/;
    say $message; # Hello, World!

If you want to interpolate the expression, use qq:

    my $audience = 'Everyone';
    my $message = qq:x/echo "Hello, $audience!"/;
    say $message; # Hello, Everyone!

With quoting mechanisms in Perl 6, the colon is optional for the first adverb so they can be slightly shortened as “qx” and “qqx”.

You’re not limited to slurping results into a single scalar either. Split strings into a list:

    # get a list of paths (also see %*ENV)
    my @env_path = qx/echo $PATH/.split(':'); # Unix-based systems 
    my @env_path = qx/echo %PATH%/.split(';'); # Windows version

Or read a program’s output into an array of lines:

    my @commit_results = qqx/git commit -am "$message"/.lines;

Or extract a particular value from tabular data:

    # requires "free" program
    my $free_memory_mb = qx/free -m/.lines[1].words[3]

These examples just scratch the surface of what’s possible, but they should give you enough to get started any time you need to run an external program. Give them a shot!

More Info

6 thoughts on “Day 6 – Running External Programs from Perl 6

  1. So how can you capture output without using the shell? For instance, if you have special characters in your git commit message and want to avoid the hassle of escaping them for the shell?

    In perl5 I aways disliked the fact that backticks always use the shell. You needed modules like IPC::System::Simple to get a safe capture method. Will it be the same in Perl 6?

  2. This is one area of Perl 6 that I wish had been designed with a little more ambition. It pretty much looks like what Perl 5 already had, except with slightly better function names and slightly better exit code handling.

    The thing is, what Perl 5 had in this area wasn’t very great.

    What I usually want when I need to run a command from a Perl script, is a function that combines all these features:

    1) short & easy to use
    2) safe parameter passing (a.k.a. no shell)
    3) checking whether it was actually run
    4) checking its return value, if it was run
    5) capturing STDOUT into a string variable
    6) suppressing STDERR (this should ideally be an option)

    In Perl 5,
    `...` does not provide (2).
    system(...) does not provide (5) and (6), and barely provides (1).
    open(..., "... |") does not provide (1) and (2).

    I came up with this custom Perl 5 function which supports all 6 features, and I use it in some of my Linux scripts, but it is probably not cross-platform because it relies on `fork`:

    
    # Try to run an external command, and return its STDOUT
    # if it was actually run, or undef if it wasn't
    sub run {
        my $pid = open my $pipe, "-|"
            // die "Failed to fork: $!";
        if ($pid) {
            my $result = do { local $/;  };
            chomp $result; waitpid $pid, 0;
            return ($? >> 8) == 127 ? undef : $result;
        }
        else {
            open STDERR, "> /dev/null";
            exec { $_[0] } @_; exit 127;
        }
    }
    
    # Usage example:
    my $output = run('foo', @args)
              // run('alternative_foo', @args)
              // die "Please install a foo program.\n"
    
    my $exit_status = ($? >> 8);
    

    Will I need to go to such lengths in Perl 6 as well, to use external commands in my workflow in a clean and safe way?

    Is it too much to asks to have something like this (in a cross-platform way of course) built into the language?

    1. It feels like we could have all this built in, though I’m not sure where to draw the line between the core and a module.

      I like your 6 features, though I’d also like to see options for getting at the STDOUT and STDERR as independent file handles, callbacks, or even Supplies.

      Ideally I think we’d combine the best features from the perl 5 IPC::Run3 and AnyEvent::Util::run_cmd, and mix it nicely with the perl existing 6 asynchrony.

  3. P.S.:
    I really like the “subprocess.check_output” function, which Python introduced in 2.7. It’s so good that it should either be blatantly copied into Perl 6 — or even improved. However Perl 6 should not be missing any features here if the competition offers them :)

  4. Splitting system() (which might or might not use shell) into run() and shell() is a step in the right direction (in Perl 5 one can use a module, the name of which escapes me ATM). However, I also wonder about execution without shell in qx/qqx, and also about capturing stderr and exit code.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.