For a complete, rock-solid implementation, see gniourf_gniourf's answer.
Here's a more concise alternative that makes do with a single invocation of find [per name to investigate].
The OP later clarified that an if statement should be used in a loop, but the question is general enough to warrant considering other approaches.
A naïve implementation would even work as a one-liner, IF you're willing to make a few assumptions (the example uses 'ls' as the executable to locate):
find -L ${PATH//:/ } -maxdepth 1 -type f -perm -u=x -name 'ls' 2>/dev/null
The assumptions - which will hold in many, but not all situations - are:
- $PATHmust not contain entries that when used unquoted result in shell expansions (e.g., no embedded spaces that would result in word splitting, no characters such as- *that would result in pathname expansion)
- $PATHmust not contain an empty entry (which must be interpreted as the current dir).
Explanation:
- -Ltells- findto investigate the targets of symlinks rather than the symlinks themselves - this ensures that symlinks to executable files are also recognized by- -type f
- ${PATH//:/ }replaces all- :chars. in- $PATHwith a space each, causing the result - due to being unquoted - to be passed as individual arguments split by spaces.
- -maxdepth 1instructs- findto only look directly in each specified directory, not also in subdirectories
- -type fmatches only files, not directories.
- -perm -u=xmatches only files and directories that the current user (- u) can execute (- x).
- 2>/dev/nullsuppresses error messages that may stem from non-existent directories in the- $PATHor failed attempts to access files due to lack of permission.
Here's a more robust script version:
Note:
- For brevity, only handles a single argument (and no options).
- Does NOT handle the case where entries or result paths may contain embedded \nchars - however, this is extremely rare in practice and likely leads to bigger problems overall.
#!//bin/bash
# Assign argument to variable; error out, if none given.
name=${1:?Please specify an executable filename.}
# Robustly read individual $PATH entries into a bash array, splitting by ':'
# - The additional trailing ':' ensures that a trailing ':' in $PATH is
#   properly recognized as an empty entry - see gniourf_gniourf's answer.
IFS=: read -r -a paths <<<"${PATH}:"
# Replace empty entries with '.' for use with `find`.
# (Empty entries imply '.' - this is legacy behavior mandated by POSIX).
for (( i = 0; i < "${#paths[@]}"; i++ )); do
  [[ "${paths[i]}" == '' ]] && paths[i]='.'
done
# Invoke `find` with *all* directories and capture the 1st match, if any, in a variable.
# Simply remove `| head -n 1` to print *all* matches.
match=$(find -L "${paths[@]}" -maxdepth 1 -type f -perm -u=x -name "$name" 2>/dev/null |
        head -n 1)
# Print result, if found, and exit with appropriate exit code.
if [[ -n $match ]]; then
  printf '%s\n' "$match"
  exit 0
else
  exit 1
fi