Should rather be:
cmd_canonical_path() {
local cmd_path
cmd_path=$(command -v -- "$1") &&
cmd_path=$(readlink -e -- "$cmd_path") &&
[ -x "$cmd_path" ] &&
REPLY=$cmd_path
} 2> /dev/null
And then use for instance as:
if cmd_canonical_path firefox; then
printf '%s\n' "firefox found in $REPLY executable"
else
printf >&2 '%s\n' "no executable file found for firefox"
fi
In shells, all commands have an exit status that indicates either success (0) or failure (any other number whose values can help discriminate between different types of failure).
For instance, the [
command, when passed -x
, /path/to/some/file
and ]
as arguments will return true if the process that executes it has execute permission for the /path/to/some/file
file (or directory).
A function can also have an exit status, and that's the exit status of the last command it ran before it returned (or the number passed to return
if called there).
The success or failure status of commands is checked by shell constructs such as if
/until
/while
statements or its boolean operators ||
and &&
. Commands are the shells' booleans if you like.
cmd1 && cmd2 && cmd3
returns success / true is all of cmd1
, cmd2
and cmd3
succeed. And cmd2
is not even executed if cmd1
fails, same for cmd3
.
So above cmd_canonical_path
succeeds if command -v -- "$1"
(the standard builtin¹ command to lookup a command and output its path²; while which
was some similar command but intended for csh users only) and then that readlink -e
(which works like readlink -f
to canonicalise³ a path but will return failure if it can't while readlink -f
will do a best effort) succeeds and then that the current process has execute permission for that file, and then (and only then) that that path is successfully assigned to the REPLY
4 variable (plain assignments are always successful).
If not, the function will return failure: the exit status of the first command that failed in that cmd && cmd && cmd
chain.
You'll also notice that $REPLY
is left alone and not modified if the canonical path to the command cannot be determined or it's not executable by you.
Some other problems with your approach:
- That
$(printf '%s\n' "$1")
doesn't make much sense. In sh/bash, it's the same as $1
(at least with the default value of $IFS
) and is equally wrong as it's not quoted. If you want to pass the contents of the first positional parameter to a command, the syntax is cmd "$1"
.
- When you want some arbitrary data you pass to a command to be treated as a non-option arguments, you need to pass it after the
--
argument that marks the end of options: which -- "$1"
, readlink -f -- "$(...)"
.
return
takes a (optional) number as argument (the exit code for the function), not true
/false
(you also seem to have them reversed as you said the function was to return true if the file was executable). If not passed a number, the function's exit status will be that of the last command run in the function. So if cmd; then return 0; else return 1; fi
is better written as just cmd; return
. Though here you don't even need the return
since cmd
([[ -x $file ]]
in your case) is the last command in your function.
- You're blindly passing the output of
which
(incorrectly as you forgot the --
and to quote the $(...)
) to readlink
without having first checked that it succeeded.
As a final note, had you used zsh
instead of bash
, you could have done:
firefox_path==firefox
firefox_canonical_path=$firefox_path:P
To get the path of the firefox
command. =firefox
expands to the path of the firefox
command and aborts the shell with an error if it can't be found. You can intercept that error in an always
block if need be. The :P
modifier does a realpath()
equivalent on the parameter it's applied to, similar to what GNU readlink -f
does. No need to check for execute permissions as zsh
's =cmd
operator would only expand to a path that is executable.
¹ and as it's a builtin of the shell, you'll find its documentation in the shell's manual. For bash
, see info bash command
for instance.
² with the caveat that command -v cmd
outputs cmd
is cmd
is a function or shell builtin which could be a problem here.
³ a canonical absolute path starts with /
, has no symlink component (they've all be resolved to their target), no .
/ ..
component, no sequences of two or more /
s.
4 $REPLY
is commonly used (though that's only a convention) as the default variable for a function or shell builtin to return a value in. Alternatively, your function could output the result, and the caller would run firefox_path=$(cmd_canonical_path firefox)
.