Как узнать, существует ли команда в соответствии с POSIX?

См. обсуждение Требуется ли команда` command -v` в оболочке POSIX? Совместим с POSIX?. Он описывает, что опция type, а также command -v необязательна в POSIX.1-2004.

Ответ, помеченный как правильный, Проверить, существует ли программа из Bash script, тоже не помогает. Точно так же, как type, hash также помечен как XSI в POSIX.1-2004. См. http://pubs.opengroup.org/onlinepubs/009695399/utilities/hash.html.

Тогда каков был бы способ, совместимый с POSIX, написать оболочку script, чтобы найти, существует ли команда в системе или нет?

Ответ 1

Как вы хотите это сделать? Вы можете найти команду в каталогах в текущем значении $PATH; вы можете посмотреть в каталогах, указанных по умолчанию для системы PATH (getconf PATH, пока getconf  существует в PATH).

Какой язык реализации вы собираетесь использовать? (Например: у меня есть реализация Perl, которая выполняет достойную работу по поиску исполняемых файлов на $PATH, но Perl не входит в POSIX, это отдаленно релевантно для вас?)

Почему бы просто не попробовать запустить его? Если вы собираетесь иметь дело с Busybox, многие исполняемые файлы не могут быть найдены путем поиска - они построены в оболочку. Главное предостережение - если команда делает что-то опасное при запуске без аргументов, но очень мало команд POSIX, если они есть, делают это. Вам также может потребоваться определить, какие статусы выхода команды указывают на то, что команда не найдена в сравнении с командой, не ссылающейся на соответствующие аргументы. И мало гарантии того, что все системы будут согласованы по этому вопросу. Это чреватый процесс, если вы не собрались.

Perl-реализация pathfile

#!/usr/bin/env perl
#
# @(#)$Id: pathfile.pl,v 3.4 2015/10/16 19:39:23 jleffler Exp $
#
# Which command is executed

# Loosely based on 'which' from Kernighan & Pike "The UNIX Programming Environment"

#use v5.10.0;    # Uses // defined-or operator; not in Perl 5.8.x
use strict;
use warnings;
use Getopt::Std;
use Cwd 'realpath';
use File::Basename;

my $arg0 = basename($0, '.pl');
my $usestr = "Usage: $arg0 [-AafhqrsVwx] [-p path] command ...\n";
my $hlpstr = <<EOS;

  -A       Absolute pathname (determined by realpath)
  -a       Print all possible matches
  -f       Print names of files (as opposed to symlinks, directories, etc)
  -h       Print this help message and exit
  -q       Quiet mode (don't print messages about files not found)
  -r       Print names of files that are readable
  -s       Print names of files that are not empty
  -V       Print version information and exit
  -w       Print names of files that are writable
  -x       Print names of files that are executable
  -p path  Use PATH
EOS

sub usage
{
    print STDERR $usestr;
    exit 1;
}

sub help
{
    print $usestr;
    print $hlpstr;
    exit 0;
}

sub version
{
    my $version = 'PATHFILE Version $Revision: 3.4 $ ($Date: 2015/10/16 19:39:23 $)';
    # Beware of RCS hacking at RCS keywords!
    # Convert date field to ISO 8601 (ISO 9075) notation
    $version =~ s%\$(Date:) (\d\d\d\d)/(\d\d)/(\d\d) (\d\d:\d\d:\d\d) \$%\$$1 $2-$3-$4 $5 \$%go;
    # Remove keywords
    $version =~ s/\$([A-Z][a-z]+|RCSfile): ([^\$]+) \$/$2/go;
    print "$version\n";
    exit 0;
}

my %opts;
usage   unless getopts('AafhqrsVwxp:', \%opts);
version if ($opts{V});
help    if ($opts{h});
usage   unless scalar(@ARGV);

# Establish test and generate test subroutine.
my $chk = 0;
my $test = "-x";
my $optlist = "";
foreach my $opt ('f', 'r', 's', 'w', 'x')
{
    if ($opts{$opt})
    {
        $chk++;
        $test = "-$opt";
        $optlist .= " -$opt";
    }
}
if ($chk > 1)
{
    $optlist =~ s/^ //;
    $optlist =~ s/ /, /g;
    print STDERR "$arg0: mutually exclusive arguments ($optlist) given\n";
    usage;
}
my $chk_ref = eval "sub { my(\$cmd) = \@_; return -f \$cmd && $test \$cmd; }";

my @PATHDIRS;
my %pathdirs;
my $path = defined($opts{p}) ? $opts{p} : $ENV{PATH};
#foreach my $element (split /:/, $opts{p} // $ENV{PATH})
foreach my $element (split /:/, $path)
{
    $element = "." if $element eq "";
    push @PATHDIRS, $element if $pathdirs{$element}++ == 0;
}

my $estat = 0;
CMD:
foreach my $cmd (@ARGV)
{
    if ($cmd =~ m%/%)
    {
        if (&$chk_ref($cmd))
        {
            print "$cmd\n" unless $opts{q};
            next CMD;
        }
        print STDERR "$arg0: $cmd: not found\n" unless $opts{q};
        $estat = 1;
    }
    else
    {
        my $found = 0;
        foreach my $directory (@PATHDIRS)
        {
            my $file = "$directory/$cmd";
            if (&$chk_ref($file))
            {
                $file = realpath($file) if $opts{A};
                print "$file\n" unless $opts{q};
                next CMD unless defined($opts{a});
                $found = 1;
            }
        }
        print STDERR "$arg0: $cmd: not found\n" unless $found || $opts{q};
        $estat = 1;
    }
}

exit $estat;