Say I have PATH="home/bob/bin:/usr/bin"
. I am writing a bash script /home/bob/bin/foo
that will do some munging and then call /usr/bin/foo
. Of course I want to be able to use this script on different systems which have different path structures. In practice the real foo might be in many different places, so I want to just find it from the PATH. My new foo script is on my path too, so I can't just call foo, that will result in a recursive call.
Is there an easy way of doing this in a bash script? (Other than looping through elements of PATH and doing the search manually?)
4 Answers 4
You can always get the path to the second foo
with:
foo=$(type -Pa foo | tail -n+2 | head -n1)
(provided file paths don't contain newline characters).
Beware that may be a relative path which would stop to be valid after you run cd
.
You could then do:
hash -p "$foo" foo
So that foo
be invoked when you run foo
.
-
You're assuming that
foo
is being executed through the PATH. This has the potential to recurse if it's executed with an explicit PATH and there's one otherfoo
in front.Gilles 'SO- stop being evil'– Gilles 'SO- stop being evil'2015年04月02日 23:40:15 +00:00Commented Apr 2, 2015 at 23:40
I don't think there's an easier way to do this robustly than enumerating the directories in PATH
. It isn't hard.
#!/bin/bash
set -f; IFS=:
for d in $PATH; do
if [[ -f $d/foo && -x $d/foo && ! $d/foo -ef /home/bob/bin/foo ]]; then
exec "$d/foo" "$@"
exit 126
fi
done
echo "0ドル: foo (real) not found in PATH"
exit 127
I assume there are no empty entries in PATH. Empty PATH entries are evil, write .
explicitly (or better don't include it at all).
If you'll only ever run foo
from the command line and not from other programs, make it a function instead of a script. From the function, run command foo
to hide the function.
-
Now that I look at it again, shouldn't it be
exec $d/foo "$@"
?muru– muru2015年04月03日 01:11:21 +00:00Commented Apr 3, 2015 at 1:11
If foo is important, then it should be configured in the script. This I think is good practice, because you then EXPLICITLY peg it to the script. I do not like indirection, implicit or hidden stuff ... scripts should be very simple if they are to be deployed.
Otherwise if you insist on finding it, then it can be found by doing
whereisfoo="$(which foo)"
If this is not enough, then I fear what you may be doing is too complex.
-
But the OP has a script called
foo
in has private bin directory ($HOME/bin
), which appears at the beginning of his$PATH
, sowhich foo
will report/home/bob/bin/foo
— which is not what the question is asking for.G-Man Says 'Reinstate Monica'– G-Man Says 'Reinstate Monica'2015年04月03日 00:07:09 +00:00Commented Apr 3, 2015 at 0:07 -
I think it is bad practice to hide stuff in scripts. It only gets you into trouble. Making the user stumble if a configuration file is not set is better practiceXofo– Xofo2015年04月29日 16:08:15 +00:00Commented Apr 29, 2015 at 16:08
-
(1) "... hide stuff in scripts"? What? I wonder whether you understand the question. (2) "configuration file"? Where did that come from? (3) "[S]cripts should be very simple if they are to be deployed." OK, that’s your opinion, which (3a) doesn’t really help answer this question, and (3b) is, I suspect, a minority opinion. Albert Einstein is credited with saying "Everything should be made as simple as possible, but not simpler." A script (or any other piece of software) should be as complicated as it needs to be to satisfy its requirements. ... (Cont’d)G-Man Says 'Reinstate Monica'– G-Man Says 'Reinstate Monica'2015年04月29日 20:52:38 +00:00Commented Apr 29, 2015 at 20:52
-
(Cont’d) ... And (I’m not sure whether this is 3c or 4) you say scripts should be very simple, but that a script that executes a
mv
command should be distributed with a configuration file that tells it whethermv
is in/bin
or/usr/bin
? Seriously?G-Man Says 'Reinstate Monica'– G-Man Says 'Reinstate Monica'2015年04月29日 20:53:24 +00:00Commented Apr 29, 2015 at 20:53 -
I do not think he is doing 'just' an mv. He is doing some munging and then calls /user/bin/foo ... Since /user/bin/foo may be somewhere else he needs to find it. I see this as bad design (my opinion). Because foo is a dependency that must be implicitly found (unbeknownst to the user).Xofo– Xofo2015年04月29日 21:36:28 +00:00Commented Apr 29, 2015 at 21:36
Here’s an approach that I believe is slightly less messy than Gilles’ answer (although I concede that to be a subjective judgment).
#!/bin/sh this_dir=$(dirname "0ドル") # Alternatively, hard-code this as this_dir=$HOME/bin redacted_PATH=$(echo ":${PATH}:" | sed -e "s:\:$this_dir\::\::" -e "s/^://" -e 's/:$//') if obscured_prog=$(PATH=$redacted_PATH which foo) then ⋮ # Munging to do before running /usr/bin/foo "$(obscured_prog)" argument(s) # may be "$@", but might not be. ⋮ # Munging to do after running /usr/bin/foo else echo "0ドル: foo (real) not found in PATH." ⋮ # Any code that you want to do anyway. fi
This constructs redacted_PATH
to be $PATH
minus the $HOME/bin
directory where this private copy of foo
lives.
echo ":${PATH}:"
adds colons to the beginning and end of $PATH
,
so every component of it will be preceded and followed by a colon —
even the first and last one. The sed
searches for
: $this_dir :
(with spaces added for "clarity") and replaces it with
:
i.e., it excises $this_dir
from ":${PATH}:"
.
It then removes the colons from the beginning and end.
Then we temporarily set PATH
to $redacted_PATH
and search for foo
using which
.
If successful, we get a full path to it (e.g., /bin/foo
or /usr/bin/foo
),
which we use to run the real (public/shared/system) copy of foo
.
Since we changed PATH
only temporarily,
/bin/foo
has access to the user’s ambient $PATH
,
and so, if /bin/foo
runs brillig
, it can find $HOME/bin/brillig
(if it exists).
This will have a problem if $HOME/bin
appears in $PATH
multiple times,
but that’s not too hard to remedy.
foo
might in turn invoke other commands, some of which may be in the same directory as thefoo
wrapper./usr/bin/foo
is accessing "private" commands like/home/bob/bin/foo
? - At best that appears to me to be a misdesign./bin/sh
with the expectation that it runs "private" commands. Or run programs in/usr/bin
that invoke an editor which is~/bin/EDITOR
. Etc.