Beefy Boxes and Bandwidth Generously Provided by pair Networks
Your skill will accomplish
what the force of many cannot
 
PerlMonks  

Finding commands in Unix PATH

by toolic (Bishop)
on Jun 04, 2008 at 17:16 UTC ( [id://690186]=CUFP: print w/replies, xml ) Need Help??

Can't remember what you called that script you wrote a couple years ago? Was it foobar ... or maybe it was fooBar ... or was it ... never mind...
% findcmd foo foo_bar

You know there's some fancy grep installed, but need to be reminded of its name. Oh, yeah...

% findcmd grep zgrep grep egrep cgrep fgrep

findcmd is a script which searches through the directories in the Unix PATH variable for executable files matching a specified regular expression. The idea for this script was not mine; I had been using the Bourne shell script of the same name from the CD from the ever-excellent book, Unix Power Tools. This script served me well for years on solaris. After switching to Linux, I would get intermittent strange behavior. So, I hacked at the sed code until it seemed to be working again... until we upgraded to the latest version of Linux. That's when I decided it was time to re-code this in Perl.

While I was at it, I decided to extend its capabilities:

  • match against a regular expression, rather than a simple string
  • option for case-sensitive matching
  • print full directory path, or just the command name

Here is the code:

#!/usr/bin/env perl use warnings; use strict; use Getopt::Long; use Pod::Usage; my $print_path; my $re; parse_args(); my @dirs = split /:/, $ENV{PATH}; for my $dirname (@dirs) { if (-d $dirname) { if (opendir my $DIR, $dirname) { my @xfiles = grep { -x "$dirname/$_" } # choose only executable +files grep { !-d "$dirname/$_" } # reject directories grep { /$re/ } # pattern match readdir $DIR; closedir $DIR; for (@xfiles) { if ($print_path) { print "$dirname/$_\n"; } else { print "$_\n"; } } } } } exit; sub parse_args { my ($help, $sens); GetOptions( 'sens' => \$sens, 'path' => \$print_path, 'help' => \$help ) or pod2usage(); $help and pod2usage(-verbose => 2); if (@ARGV) { my $pat = shift @ARGV; $re = ($sens) ? qr/$pat/ : qr/$pat/i; } else { pod2usage('Error: regex required.'); } @ARGV and pod2usage("Error: unexpected args: @ARGV"); } =head1 NAME findcmd =head1 SYNOPSIS findcmd [options] regex Options: -help Verbose help -path Print out full directory paths also -sens Case-sensitive [default is case-insensitive] =head1 DESCRIPTION Search through the directories in the Unix PATH variable for executable files matching a specified regular expression. The names of all the commands which match will be printed to STDOUT. This is based on the I<findcmd> script from the CD that comes with the I<Unix Power Tools> book. It is important to note that neither aliases nor shell built-ins (I<cd>, I<which>, etc.) will be printed since neither is found in directories in the PATH variable. Any directories listed in the PATH variable which do not exist or are not readable will be silently ignored. Duplicate directories will not be removed. =head1 ARGUMENTS =over 4 =item regex A regular expression is required. The regex may be a simple string, such as C<foo>, or it may be a more complicated expression, such as C<^foo.*bar\d>. The regex syntax is Perl; it should not be confused with shell wilcard syntax or the syntax for other common Unix utilitie +s, such as I<sed> or I<grep>. It is best to quote the regex to prevent interaction with the shell. =back =head1 OPTIONS All options can be abbreviated. =over 4 =item sens By default, the regular expression is case-insensitive. So, if the inp +ut regex is C<foo>, it will match C<foo> as well as C<FOO> and C<Foo>, et +c. To use case-sensitive, use the C<-sens> option. findcmd -sens foo =item path By default, only the command name is printed. To instead print the ful +l directory path to the command, use the C<-path> option. findcmd -path foo =item help Show this verbose usage information. =back =head1 EXAMPLES Match a command name exactly (C<gnuplot>), and print the full directory path. This provides the same functionality as the I<whereiz> utility from I<Unix Power Tools>. findcmd -sens -path '^gnuplot$' Find all executable files in all path directories: findcmd . Find commands which begin with a dot: findcmd '^\.' Find commands with strange characters (not word, period, plus, dash): findcmd '[^\w.+-]' Find commands with a literal, case-sensitive "-s": findcmd -sens '\-s' =head1 SEE ALSO Refer to the following book: Unix Power Tools 2nd Edition Jerry Peek, Tim O'Reilly and Mike Loukides O'Reilly & Associates, Inc. =cut

Constructive criticism, suggestions for improvements and bug reports are welcome.

Replies are listed 'Best First'.
Re: Finding commands in Unix PATH
by ambrus (Abbot) on Jun 04, 2008 at 18:48 UTC

    I have a method that works for this kind of thing. I press alt-star at an empty bash prompt. That combination lists every possible tab-expansion at where the cursor is, so at an empty prompt it lists all commands (including builtins, aliases, and shell functions). Then I press control-A to go to the beginning and type 'echo >a' -- I have to do this blindly because readline only displays the last screenful of the command line correctly, but that doesn't really matter. Then I type enter. This saves the lists of all commands to a temp file. Then I can search this file with a simple command like tr \  \\n <a | grep foo or something.

Re: Finding commands in Unix PATH
by Fletch (Bishop) on Jun 04, 2008 at 19:48 UTC

    Zsh keeps a hash of all commands in PATH (updated when you run hash -r and/or twiddle PATH) in a parameter.

    $ for i in ${(onk)commands}; [[ "$i" == *grep* ]] && print $i bzegrep bzfgrep bzgrep egrep fgrep git-grep grep grep-changelog msggrep pcregrep podgrep tcgrep xml_grep zegrep zfgrep zgrep zipgrep

    Granted that's a zsh pattern not a regexp (however if you really want to you can compile in pcre support when you build zsh :). Functionalization left as an exercise for the reader.

    Update: Ooop, note that that's run with setopt short_loops; without that you'd need the usual shell-y do ... done around the body.

    The cake is a lie.
    The cake is a lie.
    The cake is a lie.

      perl -le "/grep/ and print for @ARGV" ${(onk)commands}
      whatever ${(onk)commands} is/does. I'm not familiar with the peculiarities zsh.

        Zsh has several flags that affect parameter expansion. The k flag says to expand a hash into a list of its keys (rather than the default which is to expand into a list of its values; or you index it like ${commands[ls]} and get the path to ls). The o flag says to sort the resulting expansion by name in ascending order (the on is actually wrong but works; I was confuzzling glob expansion qualifiers where it's oX where X specifies what to sort on (e.g. n for filename, or m for modification time)).

        So Perl's in a run for its money with regards to line-noisy-ness compared to terse zsh. :)

        The cake is a lie.
        The cake is a lie.
        The cake is a lie.

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: CUFP [id://690186]
Approved by moritz
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others sharing their wisdom with the Monastery: (5)
As of 2024-04-19 13:59 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found