http://qs321.pair.com?node_id=11131091

hrcerq has asked for the wisdom of the Perl Monks concerning the following question:

Hi. I need feedback on this... In order to run a Perl program, I must check for the availability of another program (i.e. it's in the PATH). So I've created a subroutine for this (which returns 0 for "not found" and 1 for "found"), and I'm not sure if this is the best option. Here's the code:

sub available { my $program = shift; for my $pathdir (split /:/, $ENV{PATH}) { return 1 if -x "$pathdir/$program"; } return 0; }

I'd like to know if it's inefficient, and if there's a core module that already takes care of such tasks. Found nothing related on the FAQs.

Replies are listed 'Best First'.
Re: Check for another program availability
by pryrt (Abbot) on Apr 10, 2021 at 20:52 UTC
    if there's a core module that already takes care of such tasks

    Not core, but I use File::Which. It correctly handles some of the eccentricities of various operating systems, like correctly handling implied extensions like .bat or .exe on Windows systems. With this module, the same line of code which("perl") will correctly find my Strawberry perl.exe on Windows or /usr/bin/perl on Linux.

Re: Check for another program availability
by jwkrahn (Abbot) on Apr 10, 2021 at 21:50 UTC

    Perhaps you want Env?

    use Env '@PATH'; ... for my $pathdir ( @PATH ) { ...

      Indeed. That helps.

Re: Check for another program availability
by Tux (Canon) on Apr 11, 2021 at 07:32 UTC

    The suggested File::Which is probably *the* solution you want.

    The suggested File::Spec is probably (very) good to get used to when writing code that should work on all types of OS's.

    I want to add that returning the actual path of the tool/program/script/file is a lot more useful than returning 1.

    I also want to say that your approach is less than perfect as you do not skip empty $PATH elements and elements that do not exist or are not a directory. This matters a lot if you are dealing with environments where the environment cannot be trusted. I'd suggest (ignoring both mentioned modules):

    use List::Util qw( first ); sub available { my $prog = shift; first { -x "$_/$prog } grep { m/\S/ && -d } split m/:+/ => $ENV{PA +TH}; }

    Enjoy, Have FUN! H.Merijn

      I see. Well, thank you for your suggestions, they'll be nice improvements to my approach.

Re: Check for another program availability
by Discipulus (Canon) on Apr 10, 2021 at 20:19 UTC
    Hello hrcerq,

    I see nothing too bad in your approach. For sure is not a portable way. But you can use File::Spec to split on the correct separator and catfile in a more portable way.

    Then you can profit of the cpan module File::SearchPath to accomplish the search.

    L*

    There are no rules, there are no thumbs..
    Reinvent the wheel, then learn The Wheel; may be one day you reinvent one of THE WHEELS.
Re: Check for another program availability
by dasgar (Priest) on Apr 10, 2021 at 22:00 UTC

    The core module IPC::CMD has a can_run function that appears to do what you want. And it claims to be "platform independent".

Re: Check for another program availability (Running External Processes on Unix and Windows References)
by eyepopslikeamosquito (Archbishop) on Apr 12, 2021 at 09:57 UTC
Re: Check for another program availability
by afoken (Chancellor) on Apr 11, 2021 at 09:29 UTC

    If the program can be found in $ENV{'PATH'} when it is installed, then just trying to execute it will either succeed or fail, no need to search first. See system and exec.

    Alexander

    --
    Today I will gladly share my knowledge and experience, for there are no sweeter words than "I told you so". ;-)

      I’ll sometimes do this as an explicit pre-flight check. If so, it’s either because:

      1. I use this program deep in the code and I don’t want to have to unroll a bunch of state (or fail and leave a broken mess) if I get halfway through and then find a critical external program is missing.

      2. I want to be unusually user-friendly for a Unix program. Seriously, sometimes the failure that results “naturally” is misleading and will leave the user (usually me) scratching their head and having to troubleshoot. I can save them (myself) a bunch of time if I simply tell them (me) what's wrong.

      I agree there’s no point to checking for availability on the line before you try to run the program; you might as well just process the error from system or whatever.

      – Aaron
      Preliminary operational tests were inconclusive. (The damn thing blew up.)
Re: Check for another program availability
by hrcerq (Scribe) on Apr 10, 2021 at 21:44 UTC

    Thank you both for the portability considerations. The program is mostly focused on OpenBSD and some Linux distros, but we never know, right? Surely I'll check these File modules.

Re: Check for another program availability
by perlfan (Vicar) on Apr 11, 2021 at 21:24 UTC
    I'd avoid reimplementing how your shell uses $PATH. If this command is being run in a wrapper, it could be as simple as a commandline argument,
    $ perl ./my/script.pl $(which myprogram)

    If you don't know the name of the program at first, then,
    my $full_program = `which $program`; chomp $full_program;
    Note: which will not "find" a program in $PATH for a file where -x is not also true. In other words, which requires the "found" file to have executable permissions by the effective user. So, yeah; don't try to reimplement which.

      I see, but, would that be portable? I mean, the which program itself may not be available.

        > portable
        You didn't say it had to be "portable on Windows".
Re: Check for another program availability
by Anonymous Monk on Apr 11, 2021 at 13:37 UTC

    Second IPC::Run can_run(), which is in core.

    But if you really want to iterate over PATH, core module File::Spec has a path() that returns the components of PATH or the OS equivalent. It handles Windows (split on /;/<code> not <code>/:/) and I believe even VMS (SYS$PATH or something similar, not PATH).

      IPC::Run isn't core, and does not have a can_run method. I think you've confused this with IPC::Cmd which is, and does.