Should rather be:
cmd_path=$(command -v -- "$1") &&
cmd_path=$(readlink -e -- "$cmd_path") &&
[ -x "$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"
printf >&2 '%s\n' "no executable file found for firefox"
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
] 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
while statements or its boolean operators
&&. Commands are the shells' booleans if you like.
cmd1 && cmd2 && cmd3 returns success / true is all of
cmd3 succeed. And
cmd2 is not even executed if
cmd1 fails, same for
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
REPLY4 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:
$(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
- 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
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
[[ -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
readlink without having first checked that it succeeded.
As a final note, had you used
zsh instead of
bash, you could have done:
To get the path of the
=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
=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
info bash command for instance.
² with the caveat that
command -v cmd outputs
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
$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