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.

Jeff Schaller
  • 67,283
  • 35
  • 116
  • 255
woky
  • 432
  • 2
    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. – Thomas Dickey Mar 26 '16 at 22:34
  • @ThomasDickey Could it? From brief testing ${arr[(i)...]} only considers exact matches, or am I not understanding the issue? – intelfx Mar 14 '24 at 23:12

3 Answers3

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
Faheem Mitha
  • 35,108
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
Hannu
  • 494
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.