How to pass a regex when finding a directory path in bash?

This is a surprisingly tricky thing to do nicely.

Fundamentally, -d will only test a single argument - even if you could match filenames using a regular expression.

One way would be to flip the problem around, and test directories for a regex match instead of testing the regex match for directories. In other words, loop over all the directories in $HOME using a simple shell glob, and test each against your regex, breaking on a match, finally testing whether the BASH_REMATCH array is non-empty:

#!/bin/bash

for d in "$HOME"/*/; do
  if [[ $d =~ (ana|mini)conda[0-9]? ]]; then
    break;
  fi
done

if ((${#BASH_REMATCH[@]} > 0)); then
    echo "anaconda/miniconda directory is found in your $HOME"
  else
    echo "anaconda/miniconda is not found in your $HOME"
fi

An alternate way would be to use an extended shell glob in place of the regex, and capture any glob matches in an array. Then test if the array is non-empty:

#!/bin/bash

shopt -s extglob nullglob

dirs=( "$HOME"/@(ana|mini)conda?([0-9])/ )

if (( ${#dirs[@]} > 0 )); then
  echo "anaconda/miniconda directory is found in your $HOME"
else
  echo "anaconda/miniconda is not found in your $HOME"
fi

The trailing / ensures that only directories are matched; the nullglob prevents the shell from returning the unmatched string in the case of zero matches.


To make either recursive, set the globstar shell option (shopt -s globstar) and then respectively:-

  • (regex version): for d in "$HOME"/**/; do

  • (extended glob version): dirs=( "$HOME"/**/@(ana|mini)conda?([0-9])/ )


Indeed, as already mentioned, this is tricky. My approach is the following:

  • use find and its regex capabilities to find the directories in question.
  • let find print an x for each found directory
  • store the xes in a string
  • if the string is non-empty, then one of the directories was found.

Thus:

xString=$(find $HOME -maxdepth 1 \
                     -type d \
                     -regextype egrep \
                     -regex "$HOME/(ana|mini)conda[0-9]?" \
                     -printf 'x');
if [ -n "$xString" ]; then
    echo "found one of the directories";
else
    echo "no match.";
fi

Explanation:

  • find $HOME -maxdepth 1 finds everything below $HOME but restricts the search to one level (that is: it doesn't recurse into subdirectories).
  • -type d restricts the search to only directories
  • -regextype egrep tells find what type of regular expression we deal with. This is needed because things like [0-9]? and (…|…) are somewhat special and find doesn't recognize them by default.
  • -regex "$HOME/(ana|mini)conda[0-9]?" is the actual regular expression we want to lookout for
  • -printf 'x' just prints an x for every thing that satisfies the previous conditions.

You can loop over a list of directory names you want to test and act on it if one of them exists:

a=0
for i in {ana,mini}conda{,2}; do
  if [ -d "$i" ]; then
    unset a
    break
  fi
done
echo "anaconda/miniconda directory is ${a+not }found in your $HOME"

This solution obviously doesn’t allow for the full regex power, but shell globbing and brace expansion is equal at least in the case you showed. The loop exits as soon as one directory exists and unsets the previously set variable a. In the subsequent echo line, the parameter expansion ${a+not } expands to nothing if a is set (= no dir found) and “not ” else.