2

There's an executable /usr/bin/foo which I and other scripts use, but it misbehaves a bit so I made a Bash wrapper of the same filename in /usr/local/bin/foo where I fixed its misbehaviour. My PATH is /usr/local/bin:/usr/bin. In the wrapper script, I have to run the original executable by absolute path in order to not get into an infinite loop:

$ cat /usr/local/bin/foo
#/bin/zsh
/usr/bin/foo | grep -v "^INFO" # reduce output

Is there any (Zsh or Bash specific perhaps) straightforward way to execute next foo in PATH, that is the foo that's in PATH directories that are after the directory from which current foo was executed, so that I wouldn't have to use absolute path to the original executable?

I could make a function for it in /etc/zshenv and it's not such a big deal. I'm just wondering if there is anything standard. I don't want to use alias or fix the original executable.


EDIT 1: = $ cat /usr/local/bin/foo #/bin/zsh path=(${path/#%$0:A:h}) foo | grep -v "^INFO" # reduce output

This should empty (but keep) all strings in PATH (${path/...}) that fully match (#%) the absolute (:A) directory (:h) of the current executable($0). TBH, I assembled it from StackExchange and don't understand it fully. I hope it's not going to bite me.

EDIT 2:

$ cat /usr/local/bin/foo
#/bin/zsh
path[${path[(i)$0:A:h]}]=() foo | grep -v "^INFO" # reduce output

$path[(i)foo] finds the index of foo in the array path or 1 plus length of the array.

1
  • 1
    It could bite you if the directory happens to be a substring of some other directory name. It's probably good enough for personal use, but not for code that you expect others to use. Mar 26, 2016 at 22:34

3 Answers 3

3

You can find out what directory the script is in ($0:h in zsh, "${0%/*}" in sh) and remove that directory from PATH (path=(${path:#$0:h}) in zsh, more complicated in sh. This could fail if PATH contains the same directory twice, e.g. through a symbolic link.

A downside of the straight approach is that this removes the directory from the path, but other programs in the same directory might be desirable. You can solve this issue by doing only the path lookup with a modified path.

next=$(path=(${path:#$0:h}); print -lr -- =$0:t)
$next

Instead I'd do the PATH lookup manually and skip any occurrence of the running script.

for d in $path; do
  if [[ -x $d/$0:t && ! $d/$0:t -ef $0 ]]; then
    exec $d/$0:t
  fi
done
1

Example:
For me type -a egrep prints a user-defined alias and the actual egrep command, adding yet another egrep in my $HOME/bin/ shows it too... In the order; alias first, then the remaining items in the same order as PATH has them.

$ cd # go home

$ mkdir -p bin

$ PATH=$HOME/bin:$PATH

$ type -a egrep
egrep is aliased to `egrep --color=auto'
egrep is /bin/egrep

$ echo -e >bin/egrep '#!/bin/bash\necho TEST'

$ chmod 755 bin/egrep

$ type -a egrep
egrep is aliased to `egrep --color=auto'
egrep is /home/$USER/bin/egrep
egrep is /bin/egrep

# assuming you're sure it is the only one not in /home and not an alias
$ type -a egrep | grep -Ev '/home|alias' | cut -d' ' -f3
/bin/egrep

$ rm $HOME/bin/egrep
0

You could do:

#! /bin/zsh -

# find the other occurrences of a command with the same name in $path that
# are a different file
other_mes=(
  ${^path/#%/.}/$0:t(N*^e['[[ $REPLY -ef $0 ]]'])
)

if (( ! $#other_mes )) {
  print -ru2 Could not find any other $0:t in PATH.
  exit 1
}

# run the first found
$other_mes[1] "$@" | grep -v '^INFO'

# return the exit status of the other me, not of grep
exit $pipestatus[1]
  • ${path/#%/.} replaces the empty elements of $path with .
  • $0:t: the tail (basename) of $0
  • [[ a -ef b ]] returns true if a and b are found to be the same file after symlink resolution, either because they are 2 paths to the same file (like /usr/bin/foo vs /bin/foo because /bin happens to be a symlink to /usr/bin or ../bin/foo and $PWD is /usr/local or /usr/foo/../bin/foo, or they are hard links or symlink to each other).

With sh syntax (and a [ implementation that supports -ef, most noadays), and assuming $0 doesn't end in newline characters that could be:

#! /bin/sh -
other_me=$(
  me=$(dirname -- "$0")
  IFS=:; set -o noglob
  for dir in $PATH''; do
    other_me=${dir:-.}/$me
    if [ -x "$other_me" ] && [ -f "$other_me" ] && [ ! "$other_me" -ef "$me" ]; then
      printf '%s\n' "$other_me"
      exit
    fi
  done
  printf>&2 '%s\n' "Cound not find any other $me in PATH."
  exit 1
) || exit

{
  exit_status=$(
    {
      {
        "$other_me" "$@" 4>&-
        echo "$?" >&4 4>&-
      } 3>&- | grep -v '^INFO' >&3 3>&- 4>&-
    } 4>&1
  )
} 3>&1
exit "$exit_status"

With the convoluted bit at the end to get the exit status of $other_me.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .