Skip to content
Advertisement

Test whether at least one directory with a name prefix exists in bash

I have a script with a user-entered name, and want to determine whether any directories exist starting with that name.

This code looks like:

runTest() {
  if test -d "$name"*; then
    echo "Directories starting with $name already exist"
  fi
}

It works fine if there is only one such directory:

name=foobar
rm -rf foobar*
mkdir foobar1
runTest # now the code will work

…but it doesn’t work if there are multiple matches:

name=foobar
rm -rf foobar*
mkdir foobar1 foobar2
runTest # now the code will emit an error: "test: too many arguments"

How can I make this work with any number of directories, without assuming that one of the matches will always include a specific number (like 1)?

Advertisement

Answer

As an example of a working (bash-only — not /bin/sh compatible) alternative, consider:

if dirs=( "$name"*/ ) && [[ -d ${dirs[0]} ]]; then
  echo "Directories starting with $name already exist"
fi
  • An array assignment works with any number of matches
  • …but the first entry in the array will be a valid directory only if the glob successfully expanded (and the trailing / in the glob expression prevents it from expanding to anything except directories, so if the result has any directories at all, you know that all elements it expanded to are directories).

If you need something that works for /bin/sh as well, consider the following function:

directoriesExist() { [ -d "$1" ]; }
if directoriesExist "$name"*/;  then
  echo "Directories starting with $name already exist"
fi

This works because the list of matches expands onto the function’s argument list, effectively serving as an alternative to an array.


Because [ is a shell builtin, these globbing-based approaches have much lower constant-factor costs than approaches depending on external tools, which will make them considerably faster overall (except for some corner cases, such as directories so large that it’s preferable to stop after the first match is found; where find . -name "$name*" -print -quit may be useful).

User contributions licensed under: CC BY-SA
8 People found this is helpful
Advertisement