Linux & Bash · Part II · Module 25 · EDA Environment and Tool Access
PATH, Tool Versions and Which Binary You Got
Two versions of a tool installed side by side, one command, and two different answers decided entirely by the order of two directories. This chapter is the three commands that tell you which one you got, what each of them actually answers, and the one that is the only reliable choice in a script.
Intermediate21 min readLinuxPart IIPATHTool VersionsEDA
Part II · Module 25 · Chapter 25.2 · PATH, Tool Versions and Which Binary You Got
An EDA installation is a directory per version. Two versions on one machine is normal; five is common. Which one runs is decided by the order of directories in one environment variable, and nothing in your command line records the choice.
1. Two Versions, One Command, Two Answers
Both builds of simtool are installed, and both are on PATH:
$ export PATH=$EDA/tools/simtool-2024.09/bin:$EDA/tools/simtool-2025.03/bin:$PATH
$ command -v simtool
/home/engineer/eda/tools/simtool-2024.09/bin/simtool
$ simtool -version
simtool 2024.09 (simlib 1.4.2)Swap the two directories and nothing else:
$ export PATH=$EDA/tools/simtool-2025.03/bin:$EDA/tools/simtool-2024.09/bin:$PATH
$ simtool -version
simtool 2025.03 (simlib 1.6.0)Different tool, different library version, identical command. Both the executable and the shared library changed, because each installation carries its own lib directory — which is why "it works for me" is so often this and nothing else.
Chapter 10.2 established the lookup: the shell walks PATH left to right and takes the first match. What Part II adds is that in this industry the entries are version directories, so PATH order is a version policy.
2. Three Commands, Three Questions
| Command | Answers | Shows functions and aliases |
|---|---|---|
command -v x | "what will run?" — one answer | a bare name if it is a function |
which -a x | "what copies are on PATH?" | no — it is a program, it cannot see them |
type -a x | "everything the shell knows by this name" | yes, and first |
$ which -a simtool
/home/engineer/eda/tools/simtool-2024.09/bin/simtool
/home/engineer/eda/tools/simtool-2025.03/bin/simtool
$ type -a simtool
simtool is /home/engineer/eda/tools/simtool-2024.09/bin/simtool
simtool is /home/engineer/eda/tools/simtool-2025.03/bin/simtoolBoth list both, in PATH order, and the first line is the one that runs. which -a is the quickest way to discover that a second installation is shadowed; it is also the one you must not rely on, for the reason in §3.
3. A Function Beats PATH Entirely
$ simtool() { echo "simtool 9.9.9 (a function)"; }
$ simtool -version
simtool 9.9.9 (a function)
$ type -a simtool | head -2
simtool is a function
simtool ()The function wins, and type -a says so on its first line. which cannot: it is an external program, so it never sees the shell's functions or aliases at all — it reports the executable that would have run, which is not the same question.
This is not hypothetical in an EDA environment. Wrapper functions are how teams add default flags, select a licence file, or redirect a tool to a queue — and they are defined in a setup script you sourced an hour ago and forgot. Chapter 22.6 §2 measured a real instance where grep was such a function.
The bypass, when you want the program and not the wrapper:
$ command simtool -version
simtool 2024.09 (simlib 1.4.2)command suppresses function and alias lookup and runs the program found on PATH. In a script that must not be subverted by a user's environment, command is the defensive spelling — and so is an absolute path, which is stronger still.
4. Reading PATH as a List
A colon-separated string of fifteen long paths is unreadable. One entry per line is not:
$ echo "$PATH" | tr ':' '\n' | nl
1 /home/engineer/eda/tools/simtool-2024.09/bin
2 /usr/bin
3 /bintr ':' '\n' and nl for the position, because the number is the information — entry 1 wins over entry 2, and "which number is the tool I want" is the question. Chapter 13.5 is tr.
Three questions worth asking of a real PATH:
# which entries do not exist? (stale setup scripts accumulate these)
printf '%s\n' "$PATH" | tr ':' '\n' | while IFS= read -r d; do
[ -d "$d" ] || printf 'missing: %s\n' "$d"
done
# is anything in it twice?
printf '%s\n' "$PATH" | tr ':' '\n' | sort | uniq -d
# which entries provide a given tool?
printf '%s\n' "$PATH" | tr ':' '\n' | while IFS= read -r d; do
[ -x "$d/simtool" ] && printf '%s\n' "$d/simtool"
doneThe third is which -a written out, and worth writing out once because it shows what which -a is doing — and because it keeps working when which is absent or shadowed.
5. Putting a Specific Version on PATH
Three ways, in increasing order of how well they survive contact with other people.
Prepend, interactively:
export PATH=/tools/simtool-2024.09/bin:$PATHFine for one session. It is not idempotent — sourcing it twice gives two copies, which Chapter 25.6 measures — and it says nothing about which version a result came from.
A version variable, so the choice is visible and changeable in one place:
SIMTOOL_VERSION=${SIMTOOL_VERSION:-2024.09}
SIMTOOL_HOME=/tools/simtool-$SIMTOOL_VERSION
[ -x "$SIMTOOL_HOME/bin/simtool" ] || { printf 'no such version: %s\n' "$SIMTOOL_VERSION" >&2; return 1; }
export PATH=$SIMTOOL_HOME/bin:$PATH
export SIMTOOL_HOMEFour things that version earns: ${VAR:-default} so a caller can override it (Chapter 8.2), an existence check so a typo fails loudly rather than silently leaving the old version on PATH, SIMTOOL_HOME exported so scripts can find the installation's lib and etc without re-deriving them, and a single line to change when the project moves version.
And an absolute path recorded at setup time, which is what a flow should actually use:
SIMTOOL_BIN=$SIMTOOL_HOME/bin/simtool
export SIMTOOL_BIN
# later, in the flow:
"$SIMTOOL_BIN" -versionThis is the only form where the log can record which executable ran, because the variable is in the environment and Chapter 25.8 can capture it. A bare simtool in a flow script is a promise that PATH was right, and nothing checks it.
6. Proving It, in a Script
#!/usr/bin/env bash
# whichtool — which executable will this name resolve to, and what are the alternatives?
# usage: whichtool <name>...
# exit: 0 every name resolved to a program · 1 one did not · 2 usage
set -o nounset
[ $# -ge 1 ] || { printf 'usage: whichtool <name>...\n' >&2; exit 2; }
bad=0
for name in "$@"; do
printf '== %s\n' "$name"
p=$(command -v -- "$name" 2>/dev/null) || {
printf ' NOT FOUND on PATH\n'; bad=1; continue; }
kind=$(type -t -- "$name" 2>/dev/null || echo unknown)
printf ' resolves to %s\n' "$p"
printf ' kind %s\n' "$kind"
case $kind in
function|alias|builtin)
printf ' ** a %s shadows any program of this name\n' "$kind"
printf ' the program would be: %s\n' \
"$(command which -a -- "$name" 2>/dev/null | head -1 || echo 'none on PATH')"
bad=1 ;;
file)
real=$(cd -- "$(dirname -- "$p")" && pwd -P)/$(basename -- "$p")
[ "$real" = "$p" ] || printf ' physical %s\n' "$real"
# every other copy on PATH, in order
n=0
printf '%s\n' "$PATH" | tr ':' '\n' | while IFS= read -r d; do
[ -x "$d/$name" ] && printf ' also %s/%s\n' "$d" "$name"
done | sed '1d'
# and the version, if the tool will tell us without a licence
for f in -version --version -V -ID; do
v=$("$p" "$f" 2>/dev/null | head -1) && [ -n "$v" ] && {
printf ' version %s (via %s)\n' "$v" "$f"; break; }
done ;;
esac
done
[ "$bad" -eq 0 ]$ whichtool simtool
== simtool
resolves to /home/engineer/eda/tools/simtool-2024.09/bin/simtool
kind file
also /home/engineer/eda/tools/simtool-2025.03/bin/simtool
version simtool 2024.09 (simlib 1.4.2) (via -version)
$ echo $?
0Six decisions:
type -t for the kind, which prints exactly one word — file, function, alias, builtin, keyword — and is the cleanest way to branch on what a name is. type -a is for reading; type -t is for scripting.
A function or alias sets bad=1. That is a judgement: for the question "which binary did I get", a wrapper is an answer you need to know about, and defaulting to treating it as a problem is right in a diagnostic tool. A different tool might not agree.
The physical path is printed only when it differs. An installation reached through a current symlink — which is how most sites pin a default version — resolves to something else entirely, and that is exactly what you want recorded. Chapter 22.3 §4 is why.
| sed '1d' to drop the copy already reported, so "also" means "in addition to the one that wins".
The version loop tries four flags and stops at the first that works, because they are not standardised — Chapter 25.7 measures four tools with four different answers. The 2>/dev/null matters: a rejected flag writes to stderr and would otherwise pollute the report.
And the tool is run with no licence set, deliberately. Chapter 25.1 §4 measured that -version does not need one, which is what makes this safe to do in a diagnostic.
Practice
- Run
command -v,which -aandtype -afor a tool you use. Do all three agree? - Find a tool with two installations on your
PATH. Which one wins, and by how many positions? - Define a shell function with the same name as a tool. Confirm
whichdoes not see it andtype -adoes. - Then run the tool with
commandand confirm you get the program. - Print your
PATHone entry per line withnl. How many entries, and how many do not exist? - Check your
PATHfor duplicates withsort | uniq -d. - Write the loop that finds every copy of a tool on
PATH, and compare it withwhich -a. - In a subshell, set
PATHto/a:/band try to runls. Then leave the subshell and confirm you are fine. - Write the version-variable form from §5. Give it a version that does not exist and confirm it fails loudly.
- Record
SIMTOOL_BINin your environment and have a flow script use it. What does that let a log record? - Write
whichtooland run it on three tools, one of which is shadowed by a function. - Point it at a tool reached through a
currentsymlink and read the physical path.
Check Your Understanding
- Why is
PATHorder a version policy in an EDA environment? - What three questions do
command -v,which -aandtype -aanswer? - Why can
whichnever see a wrapper function? - How does
command -vtell you that a name is a function? - What does
command simtooldo differently fromsimtool? - Why does
nlmatter when printingPATH? - What happens if you assign a test value to
PATH, and what is the recovery? - What does a version variable buy over a bare
export PATH=? - Why should a flow use
"$SIMTOOL_BIN"rather thansimtool? - Why does
whichtoolrun the tool with no licence set?
7. Remember
- An EDA installation is a directory per version, so
PATHorder is a version policy. - Measured: the same
simtool -versiongave 2024.09 and 2025.03 from swapping twoPATHentries, changing the library too. command -vanswers what will run;which -alists the copies;type -ashows the whole truth.whichcannot see functions or aliases.type -alists them first, and first is what runs.command -vprinting a bare name means a function or builtin.command simtoolbypasses a wrapper; an absolute path is stronger.type -tfor scripting, one word:file,function,alias,builtin.- Print
PATHwithtr ':' '\n' | nl— the number is the information. - Never assign a test value to
PATH. Measured:tr: command not found. Use another variable or a subshell; recover withPATH=/usr/bin:/bin. - Use a version variable with a default and an existence check, and export a
*_HOME. - Record an absolute
*_BINfor the flow, so the log can say which executable ran. - Version flags are not standardised — try several, discard stderr.
-versionneeds no licence, which is what makes it safe in a diagnostic.
8. What Is Next
Chapter 25.3 takes the second precondition: the libraries a tool loads at run time, the error the loader produces when it cannot find one, and the measured reason the variable that fixes it behaves differently on macOS than on Linux.
Where this fits
Part of the Linux & Bash curriculum.
