Search code examples
bashperlif-statementconditional-statementssystem

Translating Bash to Perl


I am rewriting a bash script in Perl.

The original script checks to see whether a Linux package is in an unconfigured state, and deletes and reinstalls it if so.

#!/bin/bash
if [[ $(dpkg -l | grep libc-bin | grep iF) || $(dpkg -l | grep libc-dev-bin | grep iU) ]] ; then
    echo "do something"
fi

I started to see if I could use system calls and store them as variables and then just run an if statement of these multiple variables. This did not seem to work.

#!/usr/bin/perl

my $libcUnconfigured    = system("dpkg -l | grep libc-bin | grep iF");
my $libcDevUnconfigured = system("dpkg -l | grep libc-dev-bin | grep iF");

if ( $libcUnconfigured || $libcDevUnconfigured ) {
    print "Do something";
}

Solution

  • In order to receive output from an external command use the qx operator, not system which returns the exit status of the program as returned by wait.

    I suggest to use external programs only for things that you cannot do in Perl or, rarely, when they greatly simplify your work. For all else use Perl's extensive processing capabilities.

    In this case filter the return from dpkg -l by grep

    my @libcUnconfigured = grep { /libc-bin|iF/ } qx(dpkg -l);
    
    chomp @libcUnconfigured;
    
    print "Do something with $_\n" for @libUnconfigured;
    

    The qx returns a list of lines of output when used in list context, here imposed by grep. The code block in grep is run on an element at a time where each is available in the default $_ variable; the regex match is by default done on $_. The items for which the code evaluates to true pass and are returned as a list, here assigned to an array.

    Note that qx uses /bin/sh, generally relegated to another shell on your system. So put the command together carefully. See linked docs and $? in perlvar for error checking.

    Each line of output in the returned list comes with its newline, which I remove assuming some non-trivial processing with those filenames. (Wouldn't need to chomp for printing alone of course.)

    Alternatively, you can reach for one of a number of modules. A nice one is Capture::Tiny

    use warnings;
    use strict;
    use feature 'say';
    
    use Capture::Tiny qw(capture);
    
    my @cmd = qw(dpkg -l);
    
    my ($stdout, $stderr) = capture {
        system (@cmd);
    };
    
    warn "Error with @cmd: $stderr" if $stderr;
    
    say "Do something with $_" for (split /\n/, $stdout);
    

    for its clean syntax, the fact that it hands us the error, and for its ability to run nearly any code

    Capture::Tiny provides a simple, portable way to capture almost anything sent to STDOUT or STDERR, regardless of whether it comes from Perl, from XS code or from an external program.

    Here the command is formed as a list, what allows system to bypass the shell. This is better, unless you need the shell. The return in this case is a (possibly multiline) string which is thus split to process lines with package information.

    Some others, in increasing capability and complexity of use, are IPC::Run3 and IPC::Run

    Also see this entry in perlfaq8. Note that IPC::Open3 used in some examples is rather low-level.