Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to run shell command in Perl, like Raku?

Tags:

perl

raku

I have excellent code in Raku:

#!/usr/bin/env perl6
CONTROL {
    when CX::Warn {
        note $_;
        die
    }
}
use fatal;
role KeyRequired {
    method AT-KEY (\key) {
        die "Key {key} not found" unless self.EXISTS-KEY(key);
        nextsame
    }
}

sub execute ($cmd) {
    put $cmd;
    my $proc = shell $cmd, :err, :out;
    if $proc.exitcode != 0 {
        put 'exit code = ' ~ $proc.exitcode;
        put 'stderr ' ~ $proc.err.slurp;
       put 'stdout ' ~ $proc.out.slurp;
        die
    }
}

execute "ls *.p6"

I say "excellent" because the Raku version runs a command, returns an exit code, and prints stdout/stderr if needed, and all in an easily-read and easily-understood manner.

Reading through the Perl5 manual for IPC::Run https://metacpan.org/pod/IPC::Run I've come across what appears to be the best Perl5 way of doing this, but I find the methods used there to be much less easily readable and understood than the Raku way of doing things.

Reading through the manual for IPC::Run the best that I can find is:

#!/usr/bin/env perl

use strict;
use warnings FATAL => 'all';
use feature 'say';
use autodie qw(:all);
use IPC::Run qw(run timeout);

sub execute {
    my $cmd = shift;

    my @cat = ('cat', __FILE__); # Raku doesn't need to split the string into an array
    run \@cat, \undef, \my $out, \my $err, timeout( 10 ) or die "cat: $?"; 
    if ($out ne '') {
        say "\$out = $out";
    }
    if ($err ne '') {
        say "\$err = $err";
    }
}

execute("cat " . __FILE__);

execute("cat __Fle");  #intentionally wrong to produce an error

How can I re-write the Perl5 so that it is as easily read and used as the Raku code?

like image 681
con Avatar asked Jun 15 '20 20:06

con


People also ask

How to use Raku with perl5lib?

Perl use PERLLIB and PERL5LIB to specify extra search paths for modules and Both of them are ignored by Raku. Instead, you need to use RAKULIB. The directory separator also changed from ':' to ','. As with Perl, if you don't specify RAKULIB, you need to specify the library path within the program via the use lib pragma:

How to run a shell script from a Perl program?

How to run a shell script from a Perl program. 1. Using system system($command, @arguments); For example: system("sh", "script.sh", "--help" ); system("sh script.sh --help"); System will execute the $command with @arguments and return to your script when finished. You may check $! for certain errors passed to the OS by the external application.

What is the use of perlfunc in Perl?

It keeps continuing processing the script while executing the command and doesn’t wait for it to finish first, it returns false when the command is not found but never returns true. It is also a Perl function ( perlfunc) that executes a command and waits for it to get finished first and then resume the Perl script.

How do I run a file in raku terminal?

Run the file by typing raku filename.rakuinto the terminal window and hitting [Enter]. Unlike the REPL, this will not automatically print the result of each line: the code must contain a statement like sayto print output. The REPL is mostly used for trying a specific piece of code, typically a single line.


Video Answer


1 Answers

You've unfairly loaded the Perl 5 example with a lot of extra fluff, and you haven't handled many things in the Raku code. For instance, you output the results in Raku despite what's in the variables, but test the variables in Perl 5.

Your Perl 5 would look more like this:

use v5.30;
use IPC::Run qw(run timeout);

sub execute {
    my @command = @_;
    run \@command, \undef, \my $out, \my $err, timeout( 10 ) 
        or die "cat: $?"; 
    say "\$out = $out";
    say "\$err = $err";
}

execute("cat ", __FILE__);

ikegami offered this version in his pastebin link:

sub execute {
    my ($command) = @_;
    if (! run $command, \undef, \my $out, \my $err, timeout( 10 ) ) {
       say "exit code = $?";
       say "stderr $err";
       put "stdout $out";
       die "Died";
   }
}

There's an interesting thing to note in both of those cases. You are assuming an error if the exit code is not zero (and Raku assumes that, which is why you have to worry about not sinking the result). However, many useful programs don't follow that convention. For instance, git merge base uses exit value 1 to mean "not an ancestor" and all exit values higher than 1 to mean an error. The command-line grep is similar. sendmail had exit code 75 to mean that something didn't work out, but it would try again later.

Raku, having an opinion on that, ignores this sort of thing and does not allow you to tell the Proc which exit values it should accept as successful exits. Perl 5 is not so opinionated. Using or die or ! ... is really saying "exit code is not zero", but that's not really a good enough description. In many cases you get away with it, but at least Perl 5 isn't deciding for you. If you expanded the Raku example to check the literal value and decide if that's successful, it will look messy.


But, notice that Raku's shell documentation notes that it's unsafe and that you should use run instead.

For what it's worth, I don't find Raku's interprocess communication all that trustworthy. In many cases, I think its IPC design was neglected. See, for instance, Does changing Perl 6's $*OUT change standard output for child processes? . I have several other IPC questions spread out in bug reports and in Stackoverflow, and almost none of them received a satisfactory answer. Mostly, I think that's because nobody thought about it that much. Granted, Raku is developed by a small team and its a big project, but when it comes to production programming, that's no factor.

Some more Raku shell weirdness:

  • Which shell does Perl 6's shell() use?
like image 142
brian d foy Avatar answered Oct 16 '22 11:10

brian d foy