Skip to content
VLSI Mentor

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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
$ 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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
$ 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

CommandAnswersShows functions and aliases
command -v x"what will run?" — one answera 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
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
$ 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/simtool

Both 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

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
$ 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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
$ 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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
$ echo "$PATH" | tr ':' '\n' | nl
     1	/home/engineer/eda/tools/simtool-2024.09/bin
     2	/usr/bin
     3	/bin

tr ':' '\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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
# 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"
done

The 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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
export PATH=/tools/simtool-2024.09/bin:$PATH

Fine 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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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_HOME

Four 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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
SIMTOOL_BIN=$SIMTOOL_HOME/bin/simtool
export SIMTOOL_BIN
# later, in the flow:
"$SIMTOOL_BIN" -version

This 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

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
#!/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 ]
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
$ 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 $?
0

Six 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

  1. Run command -v, which -a and type -a for a tool you use. Do all three agree?
  2. Find a tool with two installations on your PATH. Which one wins, and by how many positions?
  3. Define a shell function with the same name as a tool. Confirm which does not see it and type -a does.
  4. Then run the tool with command and confirm you get the program.
  5. Print your PATH one entry per line with nl. How many entries, and how many do not exist?
  6. Check your PATH for duplicates with sort | uniq -d.
  7. Write the loop that finds every copy of a tool on PATH, and compare it with which -a.
  8. In a subshell, set PATH to /a:/b and try to run ls. Then leave the subshell and confirm you are fine.
  9. Write the version-variable form from §5. Give it a version that does not exist and confirm it fails loudly.
  10. Record SIMTOOL_BIN in your environment and have a flow script use it. What does that let a log record?
  11. Write whichtool and run it on three tools, one of which is shadowed by a function.
  12. Point it at a tool reached through a current symlink and read the physical path.

Check Your Understanding

  1. Why is PATH order a version policy in an EDA environment?
  2. What three questions do command -v, which -a and type -a answer?
  3. Why can which never see a wrapper function?
  4. How does command -v tell you that a name is a function?
  5. What does command simtool do differently from simtool?
  6. Why does nl matter when printing PATH?
  7. What happens if you assign a test value to PATH, and what is the recovery?
  8. What does a version variable buy over a bare export PATH=?
  9. Why should a flow use "$SIMTOOL_BIN" rather than simtool?
  10. Why does whichtool run the tool with no licence set?

7. Remember

  • An EDA installation is a directory per version, so PATH order is a version policy.
  • Measured: the same simtool -version gave 2024.09 and 2025.03 from swapping two PATH entries, changing the library too.
  • command -v answers what will run; which -a lists the copies; type -a shows the whole truth.
  • which cannot see functions or aliases. type -a lists them first, and first is what runs.
  • command -v printing a bare name means a function or builtin.
  • command simtool bypasses a wrapper; an absolute path is stronger.
  • type -t for scripting, one word: file, function, alias, builtin.
  • Print PATH with tr ':' '\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 with PATH=/usr/bin:/bin.
  • Use a version variable with a default and an existence check, and export a *_HOME.
  • Record an absolute *_BIN for the flow, so the log can say which executable ran.
  • Version flags are not standardised — try several, discard stderr.
  • -version needs 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.