1

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

2 Answers 2

1

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.
Sign up to request clarification or add additional context in comments.

1 Comment

Ah, that makes sense. So do you know if there's a way to do what I want with parameter expansion? It seems like it may not be possible with bash. I've seen something like what I want accomplished with zsh: for i in ${(M)ar:#ba*}, which is I guess where I got the idea.
1

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
)

1 Comment

Thanks for the input. The first two solutions rely on knowing all of the characters that were removed, so they don't work well if I try to replace ba* with *ba*. The last three solutions work with a wildcard at the beginning, but the printfs don't give me much flexibility. The last solution gives the best of both worlds but switch syntax is so ugly in shell -- it seems like a lateral move compared to my working solution. Out of curiosity, what are your paren blocks for? Does this scope local variables?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.