michaelxor
michaelxor

Reputation: 707

Bash: Using Array Parameter Expansion to Include Items Matching Pattern

I would like to use bash parameter expansion on an array to include items matching a given pattern, rather than to exclude them.

Here's an example excluding the items matching ba*:

ar=( foo bar baz qux )
for i in ${ar[@]##ba*}; do
    echo "$i"
done

# foo
# qux

That's all well and good, but in this case I'd actually like to include only the items matching ba*.

Here's what I've tried, and frankly, I'm not quite sure how to interpret the results:

ar=( foo bar baz qux )
#shopt -s extglob  # seems to have no effect either way
for i in ${ar[@]##!(ba*)}; do
    echo "$i"
done

# ar
# az

It looks like I'm in this case I'm getting the right items, but their values have been munged.

Here's my current working solution:

ar=( foo bar baz qux )
for i in ${ar[@]}; do
    if [[ "$i" =~ ba* ]]; then
        echo "$i"
    fi
done

# bar
# baz

I am using Mac OSX and have tested this with Mac bundled bash 3.2.51 as well as Homebrew installed bash 4.2.45.

Thanks!

Edit

@casc (rightly) suggests below to just re-append the removed 'b' from the matches, but that's not exactly what I'm looking for.

My actual use case would be more like including items that match *ba*, not just ba*. The point is that I don't necessarily know the full string, just that it will definitely have the pattern ba* somewhere. This may be a better example:

ar=( foo zoobar waybaz qux )
for i in ${ar[@]}; do
    if [[ "$i" =~ ba* ]]; then
        # do something with $i
    fi
done

# zoobar
# waybaz

Upvotes: 1

Views: 285

Answers (2)

casc
casc

Reputation: 11

Why not simply prepend the chopped-off b again?

(
shopt -s extglob
ar=( foo bar baz qux )
for i in ${ar[@]/#!(ba*)}; do
   echo "b${i}"
done
)

# ... or ...

(
export IFS=""
ar=( foo bar baz qux )
ar=( ${ar[@]/#!(ba*)} )
ar=( ${ar[@]/#/b} )
echo ${!ar[*]}
printf '%s\n' "${ar[@]}"
)

Further alternatives include:

ar=( foo bar baz qux )
printf '%s\n' "${ar[@]}" "${ar[@]##ba*}" | sort | uniq -u
printf '%s\n' "${ar[@]}" "${ar[@]/#ba*}" | sort | uniq -u


# alternative to current working solution 
(
array=( foo bar baz qux )
for item in "${ar[@]}"; do
   case "$item" in
      ba*) echo "$item";;
        *) :;;
   esac
done
)

Upvotes: 1

chepner
chepner

Reputation: 531490

From each element of the array, you are removing the longest prefix that does not match ba*.

  1. For foo and qua, the entire string does not match.
  2. For bar and baz, b does not match ba*, but ba does, so only b is removed.

Upvotes: 1

Related Questions