1

i have several patterns that include ** wildcard, let them be:

foo/**/bar.xml
foo/**/baz.xml

and i have an example dirtree:

$ tree foo/
foo/
└── deep
    ├── baz.xml
    └── even
        └── deeper
            └── baz.xml

what would be easiest way to check if any of those patterns is fulfilled? to be precise: return nonzero only if none is found. what I came up with so far is to use globstar and nullglob. But ls is not an option here, as if no pattern is satisfied, is lists current directory. So i had to utilize echo, like this:

$ (shopt -s globstar nullglob; echo foo/**/bar.xml foo/**/baz.xml) | grep .
foo/deep/baz.xml foo/deep/even/deeper/baz.xml
$ echo $?
0

vs

$ (shopt -s globstar nullglob; echo foo/**/bar.xml foo/**/beque.xml) | grep .
$ echo $?
1

so yeah - it works. but maybe i missed something and there is simpler/more elegant way to achieve this?

2 Answers 2

1

You could use a function:

non_empty() (($#))
has() (
  IFS=
  set +o noglob
  shopt -s globstar nullglob extglob
  shopt -u failglob # which has precedence over nullglob!
  for glob do
    non_empty $glob && return
  done
  false
)
if has 'foo/**/bar.xml' 'foo/**/baz.xml'; then
  echo matches found
fi

(or has 'foo/**/ba[rz].xml' or (with extglob) has 'foo/**/@(bar|baz).xml' or has 'foo/**/bar.xml' || has 'foo/**/baz.xml')

It stops at the first glob that has a match.

To make it more efficient and stop at the first match, you could use zsh instead and its Y1 glob qualifier:

has() {
  local glob
  for glob do
    ()(($#)) $~glob(NY1) && return
  done
  false
}

(also inlining the non_empty into an anonymous function).

**/ for recursive does come from zsh in the early nineties and enabled by default there (copied by bash but not enabled by default in the late 2000s). Alternation is done with has 'foo/**/(bar|baz).xml' in zsh.

In zsh, you can also do:

alias has='noglob has'

So as to be able to do:

if has foo/**/bar.xml foo/**/baz.xml; then
  echo matches found
fi

Without having to quote the glob operators.

0

If it's ok to overwrite the list of positional parameters:

shopt -s globstar nullglob

set -- foo/**/bar.xml foo/**/baz.xml

if [ "$#" -ne 0 ]; then
    echo matches found
fi

This sets the list of positional parameters to the names matching the globbing pattern. Since nullglob is in effect, the list will be empty if no matches are found. The value of $# is the length of the list of positional parameters.

Using your idea of running it in a subshell:

( shopt -s globstar nullglob; set -- foo/**/bar.xml foo/**/baz.xml; [ "$#" -ne 0 ] )

Using an array in place of the list of positional parameters:

shopt -s globstar nullglob

names=( foo/**/bar.xml foo/**/baz.xml )

if [ "${#names[@]}" -ne 0 ]; then
    echo matches found
fi

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.