Detect if PATH has a specific directory entry in it

BashShellPath

Bash Problem Overview


With /bin/bash, how would I detect if a user has a specific directory in their $PATH variable?

For example

if [ -p "$HOME/bin" ]; then
  echo "Your path is missing ~/bin, you might want to add it."
else
  echo "Your path is correctly set"
fi

Bash Solutions


Solution 1 - Bash

Using grep is overkill, and can cause trouble if you're searching for anything that happens to include RE metacharacters. This problem can be solved perfectly well with bash's builtin [[ command:

if [[ ":$PATH:" == *":$HOME/bin:"* ]]; then
  echo "Your path is correctly set"
else
  echo "Your path is missing ~/bin, you might want to add it."
fi

Note that adding colons before both the expansion of $PATH and the path to search for solves the substring match issue; double-quoting the path avoids trouble with metacharacters.

Solution 2 - Bash

Here's how to do it without grep:

if [[ $PATH == ?(*:)$HOME/bin?(:*) ]]

The key here is to make the colons and wildcards optional using the ?() construct. There shouldn't be any problem with metacharacters in this form, but if you want to include quotes this is where they go:

if [[ "$PATH" == ?(*:)"$HOME/bin"?(:*) ]]

This is another way to do it using the match operator (=~) so the syntax is more like grep's:

if [[ "$PATH" =~ (^|:)"${HOME}/bin"(:|$) ]]

Solution 3 - Bash

There is absolutely no need to use external utilities like grep for this. Here is what I have been using, which should be portable back to even legacy versions of the Bourne shell.

case :$PATH: # notice colons around the value
  in *:$HOME/bin:*) ;; # do nothing, it's there
     *) echo "$HOME/bin not in $PATH" >&2;;
esac

Solution 4 - Bash

Something really simple and naive:

echo "$PATH"|grep -q whatever && echo "found it"

Where whatever is what you are searching for. Instead of && you can put $? into a variable or use a proper if statement.

Limitations include:

  • The above will match substrings of larger paths (try matching on "bin" and it will probably find it, despite the fact that "bin" isn't in your path, /bin and /usr/bin are)
  • The above won't automatically expand shortcuts like ~

Or using a perl one-liner:

perl -e 'exit(!(grep(m{^/usr/bin$},split(":", $ENV{PATH}))) > 0)' && echo "found it"

That still has the limitation that it won't do any shell expansions, but it doesn't fail if a substring matches. (The above matches "/usr/bin", in case that wasn't clear).

Solution 5 - Bash

Here's a pure-bash implementation that will not pick up false-positives due to partial matching.

if [[ $PATH =~ ^/usr/sbin:|:/usr/sbin:|:/usr/sbin$ ]] ; then
  do stuff
fi

What's going on here? The =~ operator uses regex pattern support present in bash starting with version 3.0. Three patterns are being checked, separated by regex's OR operator |.

All three sub-patterns are relatively similar, but their differences are important for avoiding partial-matches.

In regex, ^ matches to the beginning of a line and $ matches to the end. As written, the first pattern will only evaluate to true if the path it's looking for is the first value within $PATH. The third pattern will only evaluate to true if if the path it's looking for is the last value within $PATH. The second pattern will evaluate to true when it finds the path it's looking for in-between others values, since it's looking for the delimiter that the $PATH variable uses, :, to either side of the path being searched for.

Solution 6 - Bash

I wrote the following shell function to report if a directory is listed in the current PATH. This function is POSIX-compatible and will run in compatible shells such as Dash and Bash (without relying on Bash-specific features).

It includes functionality to convert a relative path to an absolute path. It uses the readlink or realpath utilities for this but these tools are not needed if the supplied directory does not have .. or other links as components of its path. Other than this, the function doesn’t require any programs external to the shell.

# Check that the specified directory exists – and is in the PATH.
is_dir_in_path()
{
  if  [ -z "${1:-}" ]; then
    printf "The path to a directory must be provided as an argument.\n" >&2
    return 1
  fi

  # Check that the specified path is a directory that exists.
  if ! [ -d "$1" ]; then
    printf "Error: ‘%s’ is not a directory.\n" "$1" >&2
    return 1
  fi

  # Use absolute path for the directory if a relative path was specified.
  if command -v readlink >/dev/null ; then
    dir="$(readlink -f "$1")"
  elif command -v realpath >/dev/null ; then
    dir="$(realpath "$1")"
  else
    case "$1" in
      /*)
        # The path of the provided directory is already absolute.
        dir="$1"
      ;;
      *)
        # Prepend the path of the current directory.
        dir="$PWD/$1"
      ;;
    esac
    printf "Warning: neither ‘readlink’ nor ‘realpath’ are available.\n"
    printf "Ensure that the specified directory does not contain ‘..’ in its path.\n"
  fi

  # Check that dir is in the user’s PATH.
  case ":$PATH:" in
    *:"$dir":*)
      printf "‘%s’ is in the PATH.\n" "$dir"
      return 0
      ;;
    *)
      printf "‘%s’ is not in the PATH.\n" "$dir"
      return 1
      ;;
  esac
}

The part using :$PATH: ensures that the pattern also matches if the desired path is the first or last entry in the PATH. This clever trick is based upon this answer by Glenn Jackman on Unix & Linux.

Solution 7 - Bash

This is a brute force approach but it works in all cases except when a path entry contains a colon. And no programs other than the shell are used.

previous_IFS=$IFS
dir_in_path='no'
export IFS=":"
for p in $PATH
do
  [ "$p" = "/path/to/check" ] && dir_in_path='yes'
done

[ "$dir_in_path" = "no" ] && export PATH="$PATH:/path/to/check"
export IFS=$previous_IFS

Solution 8 - Bash

$PATH is a list of strings separated by : that describe a list of directories. A directory is a list of strings separated by /. Two different strings may point to the same directory (like $HOME and ~, or /usr/local/bin and /usr/local/bin/). So we must fix the rules of what we want to compare/check. I suggest to compare/check the whole strings, and not physical directories, but remove duplicate and trailing /.

First remove duplicate and trailing / from $PATH:

echo $PATH | tr -s / | sed 's//:/:/g;s/:/\n/g'
Now suppose $d contains the directory you want to check. Then pipe the previous command to check $d in $PATH.
echo $PATH | tr -s / | sed 's//:/:/g;s/:/\n/g' | grep -q "^$d$" || echo "missing $d"

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionepochwolfView Question on Stackoverflow
Solution 1 - BashGordon DavissonView Answer on Stackoverflow
Solution 2 - BashDennis WilliamsonView Answer on Stackoverflow
Solution 3 - BashtripleeeView Answer on Stackoverflow
Solution 4 - BashAdam BatkinView Answer on Stackoverflow
Solution 5 - BashJekotiaView Answer on Stackoverflow
Solution 6 - BashAnthony GeogheganView Answer on Stackoverflow
Solution 7 - BashbmacnaughtonView Answer on Stackoverflow
Solution 8 - Bashjcr38View Answer on Stackoverflow