LinuxQuestions.org
Share your knowledge at the LQ Wiki.
Home Forums Tutorials Articles Register
Go Back   LinuxQuestions.org > Forums > Non-*NIX Forums > Programming
User Name
Password
Programming This forum is for all programming questions.
The question does not have to be directly related to Linux and any language is fair game.

Notices


Reply
  Search this Thread
Old 04-13-2017, 10:56 AM   #1
orbea
Senior Member
 
Registered: Feb 2015
Distribution: Slackware64-current
Posts: 1,950

Rep: Reputation: Disabled
'command -v' for true bourne shells


As a way to learn I have been playing with different shells to see how portable I could do something and then came across this discussion on 'which' vs. 'command -v'.

https://unix.stackexchange.com/quest...at-to-use-then

Suffice to say I was not very happy with command -v, especially given it does not always have the same behavior in different shells (See mksh). So with some effort and several different iterations I came up with this function that serves most of my needs while working with at least ash (busybox and netbsd), bash, dash, ksh93, mksh, pdksh, yash and zsh.

Code:
exists () {
  v=1
  while [ "$#" -gt 0 ]; do
    arg="$1"; shift
    case "$arg" in ''|*/) continue ;; esac
    x="${arg##*/}" z="${arg%/*}"
    [ "$x" != "$z" ] || [ "$z/$x" = "$arg" ] && [ ! -f "$z/$x" ] && continue 
    [ "$x" = "$z" ] && [ -x "$z/$x" ] && [ ! -f "$arg" ] && z=
    p="$z:$PATH"
    while [ "$p" != "${p#*:}" ]; do
      d="${p%%:*}" p="${p#*:}" b="$d/$x"
      [ -f "$b" ] && [ -x "$b" ] && printf %s\\n "$b" && v=0 && break
    done
  done
  return "$v"
}
Usage:
Code:
$ exists cat
/usr/bin/cat

$ exists cat ls awk
/usr/bin/cat
/usr/bin/ls
/usr/bin/awk

$ exists foo
$ echo $?
1

$ exists foo cat
/usr/bin/cat
$ echo $?
0
However I then wondered if this would work on even older true bourne shells (See hsh and osh) and it did not. The problem was the splitting of variables with "${foo##*bar}" which these limited shells did not have, note that they do not even have 'command'...

So I then changed it to use cut instead which does work. Though this predictably suffered from the problem of calling cut several times and a rather noticeable performance drop.

Code:
exists () {
  v=1
  while [ "$#" -gt 0 ]; do
    arg="$1"; shift
    case "$arg" in ''|*/) continue ;; esac
    x="`printf %s "$arg" | cut -d '/' -f 2-`"
    z="`printf %s "$arg" | cut -d '/' -f 1`"
    [ "$x" != "$z" ] || [ "$z/$x" = "$arg" ] && [ ! -f "$z/$x" ] && continue
    [ "$x" = "$z" ] && [ -x "$z/$x" ] && [ ! -f "$arg" ] && z=
    p="$z:$PATH"
    while [ "$p" != "`printf %s "$p" | cut -d ':' -f 1`" ]; do
      d="`printf %s "$p" | cut -d ':' -f 1`"
      p="`printf %s "$p" | cut -d ':' -f 2-`"
      [ -f "$d/$x" ] && [ -x "$d/$x" ] && printf %s\\n "$d/$x" && v=0 && break
    done
  done
  return "$v"
}
So my question is there a better way of doing this? Or is it as good as it gets?
 
Old 04-13-2017, 12:00 PM   #2
grail
LQ Guru
 
Registered: Sep 2009
Location: Perth
Distribution: Manjaro
Posts: 10,006

Rep: Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191
Do you have access to the 'type' command? I have previously used it in place of 'which'.
It is not pretty but seemed to work, you may have to change it to be more posix correct:
Code:
#!/bin/bash

#   Used to find the location of an executable
#
#   Usage: which [paramters] executable
#

usage()
{
	cat<<-USAGE
		 
		Which is designed to tell the user where an executable is located

		Usage:      which [options] executable
		Example:    which bash

		Options:

		-i			Ignore commands not found
		-h      Display this information
		 
		USAGE

	exit 1
} #end usage

ignore=0

if (( $# == 0 ))
then
    usage
else
    while getopts ":hi" option
    do
        case $option in
					  i)  ignore=1;;
            *)  usage;;
        esac
    done

    shift $((OPTIND - 1))

    for EXE
    do
        if ! type -a $EXE 2>/dev/null
        then
						NOEXE+="$EXE\n"
        fi
    done

    (( ${#NOEXE[*]} > 0 && ignore == 0 )) && echo -e "\nThe following were not found:\n\e[1;33m${NOEXE[*]}\e[0m" >&2
fi

exit 0
 
Old 04-13-2017, 12:51 PM   #3
MadeInGermany
Senior Member
 
Registered: Dec 2011
Location: Simplicity
Posts: 2,790

Rep: Reputation: 1201Reputation: 1201Reputation: 1201Reputation: 1201Reputation: 1201Reputation: 1201Reputation: 1201Reputation: 1201Reputation: 1201
Traditionally you simply set PATH at the beginning of the script, then run the plain commands.
If you have incompatble commands in different directories, you might need to think a bit about their order in the PATH.
 
1 members found this post helpful.
Old 04-13-2017, 12:54 PM   #4
orbea
Senior Member
 
Registered: Feb 2015
Distribution: Slackware64-current
Posts: 1,950

Original Poster
Rep: Reputation: Disabled
In hsh type is expectingly limited...

Code:
type name ...
             For each name, prints if it would be executed  as  a  shell
             function,  as a special command, or as an external command.
             In the last case, the full path name to the command is also
             printed.
Also if the goal is portability, type is problematic if you consider pdksh and mksh.

Code:
$ pdksh 
$ type type
type is an alias for 'whence -v'

$ mksh
$ type type
type is an alias for '\builtin whence -v'
And if you would start a script with 'unalias -a'...

Not to mention with hsh you don't have 'unalias' or even 'alias'.
 
Old 04-13-2017, 01:11 PM   #5
orbea
Senior Member
 
Registered: Feb 2015
Distribution: Slackware64-current
Posts: 1,950

Original Poster
Rep: Reputation: Disabled
Quote:
Originally Posted by MadeInGermany View Post
Traditionally you simply set PATH at the beginning of the script, then run the plain commands.
If you have incompatble commands in different directories, you might need to think a bit about their order in the PATH.
I think this should be a safe way to do that in pretty much every posix shell short of bash and zsh where 'unset' can be a function. This is a variation of what is explained in the posix spec for 'command' under examples.

Code:
un\set -f command unalias 2>/dev/null
un\alias -a
PATH="$(command -p getconf PATH):$PATH"
Additionally can set IFS to (space)(tab)(newline) at the top.
 
Old 06-23-2017, 08:25 AM   #6
orbea
Senior Member
 
Registered: Feb 2015
Distribution: Slackware64-current
Posts: 1,950

Original Poster
Rep: Reputation: Disabled
I just want to mention the original function had some bugs, here is a corrected version.
Code:
exists () {
  v=1
  while [ "$#" -gt 0 ]; do
    arg="$1"; shift
    case "$arg" in ''|*/) continue ;; esac
    x="${arg##*/}" z="${arg%/*}"
    [ ! -f "$z/$x" ] || [ ! -x "$z/$x" ] && [ "$z/$x" = "$arg" ] && continue
    [ "$x" = "$z" ] && [ -x "$z/$x" ] && [ ! -f "$arg" ] && z=
    p=":$z:$PATH"
    while [ "$p" != "${p#*:}" ]; do
      p="${p#*:}"; d="${p%%:*}"; b="$d/$x"
      [ -f "$b" ] && [ -x "$b" ] && printf %s\\n "$b" && v=0 && break
    done
  done
  return "$v"
}
 
  


Reply



Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is Off
HTML code is Off



Similar Threads
Thread Thread Starter Forum Replies Last Post
command to list the no. of shells lipun4u Linux - Newbie 9 02-15-2010 01:36 AM
reading command output by line in Bourne script? ocicat Programming 6 02-14-2010 02:48 AM
executing Bourne shell variable as command? ocicat Programming 3 07-31-2007 02:00 AM

LinuxQuestions.org > Forums > Non-*NIX Forums > Programming

All times are GMT -5. The time now is 03:39 AM.

Main Menu
Advertisement
My LQ
Write for LQ
LinuxQuestions.org is looking for people interested in writing Editorials, Articles, Reviews, and more. If you'd like to contribute content, let us know.
Main Menu
Syndicate
RSS1  Latest Threads
RSS1  LQ News
Twitter: @linuxquestions
Open Source Consulting | Domain Registration