Erik
Erik

Reputation: 7342

Prefix and postfix elements of a bash array

I want to pre- and postfix an array in bash similar to brace expansion.

Say I have a bash array

ARRAY=( one two three )

I want to be able to pre- and postfix it like the following brace expansion

echo prefix_{one,two,three}_suffix

The best I've been able to find uses bash regex to either add a prefix or a suffix

echo ${ARRAY[@]/#/prefix_}
echo ${ARRAY[@]/%/_suffix}

but I can't find anything on how to do both at once. Potentially I could use regex captures and do something like

echo ${ARRAY[@]/.*/prefix_$1_suffix}

but it doesn't seem like captures are supported in bash variable regex substitution. I could also store a temporary array variable like

PRE=(${ARRAY[@]/#/prefix_})
echo ${PRE[@]/%/_suffix}

This is probably the best I can think of, but it still seems sub par. A final alternative is to use a for loop akin to

EXPANDED=""
for E in ${ARRAY[@]}; do
    EXPANDED="prefix_${E}_suffix $EXPANDED"
done
echo $EXPANDED

but that is super ugly. I also don't know how I would get it to work if I wanted spaces anywhere the prefix suffix or array elements.

Upvotes: 46

Views: 23026

Answers (7)

GrabbenD
GrabbenD

Reputation: 520

Bash 5.2 uses substring matching by default (see patsub_replacement):

$ a=(1 2 3)
$ echo "${a[@]/*/'prefix'&'suffix'}"
prefix1suffix prefix2suffix prefix3suffix

Upvotes: 4

Dennis
Dennis

Reputation: 11

Perhaps this would be the most elegant solution:

$ declare -a ARRAY=( one two three )
$ declare -p ARRAY
declare -a ARRAY=([0]="one" [1]="two" [2]="three")
$
$ IFS=$'\n' ARRAY=( $(printf 'prefix %s_suffix\n' "${ARRAY[@]}") )
$
$ declare -p ARRAY
declare -a ARRAY=([0]="prefix one_suffix" [1]="prefix two_suffix" [2]="prefix three_suffix")
$
$ printf '%s\n' "${ARRAY[@]}"
prefix one_suffix
prefix two_suffix
prefix three_suffix
$

By using IFS=$'\n' in front of the array reassignment (being valid only for this assignment line), it is possible to preserve spaces in both prefix & suffix as well as array element strings.

Using "printf" is rather handy, because it allows to apply the format string (1st argument) to each additional string argument supplied to the call of "printf".

Upvotes: 1

Niklas Holm
Niklas Holm

Reputation: 1028

Prettier but essentially the same as the loop solution:

$ ARRAY=(A B C)
$ mapfile -t -d $'\0' EXPANDED < <(printf "prefix_%s_postfix\0" "${ARRAY[@]}")
$ echo "${EXPANDED[@]}"
prefix_A_postfix prefix_B_postfix prefix_C_postfix

mapfile reads rows into elements of an array. With -d $'\0' it instead reads null-delimited strings and -t omits the delimiter from the result. See help mapfile.

Upvotes: 3

Mingye Wang
Mingye Wang

Reputation: 1374

Bash brace expansion don't use regexes. The pattern used is just some shell glob, which you can find in bash manual 3.5.8.1 Pattern Matching.

Your two-step solution is cool, but it needs some quotes for whitespace safety:

ARR_PRE=("${ARRAY[@]/#/prefix_}")
echo "${ARR_PRE[@]/%/_suffix}"

You can also do it in some evil way:

eval "something $(printf 'pre_%q_suf ' "${ARRAY[@]}")"

Upvotes: 29

bruin
bruin

Reputation: 1231

I have exactly the same question, and I come up with the following solution using sed's word boundary match mechanism:

myarray=( one two three )
newarray=( $(echo ${myarray[*]}|sed "s/\(\b[^ ]\+\)/pre-\1-post/g") )
echo ${newarray[@]}
> pre-one-post pre-two-post pre-three-post
echo ${#newarray[@]}
> 3

Waiting for more elegant solutions...

Upvotes: -1

Ryba
Ryba

Reputation: 701

For arrays:

ARRAY=( one two three )
(IFS=,; eval echo prefix_\{"${ARRAY[*]}"\}_suffix)

For strings:

STRING="one two three"
eval echo prefix_\{${STRING// /,}\}_suffix

eval causes its arguments to be evaluated twice, in both cases first evaluation results in

echo prefix_{one,two,three}_suffix

and second executes it. For array case subshell is used to avoid overwiting IFS

You can also do this in zsh:

echo ${${ARRAY[@]/#/prefix_}/%/_suffix}

Upvotes: 1

John Kugelman
John Kugelman

Reputation: 361605

Your last loop could be done in a whitespace-friendly way with:

EXPANDED=()
for E in "${ARRAY[@]}"; do
    EXPANDED+=("prefix_${E}_suffix")
done
echo "${EXPANDED[@]}"

Upvotes: 18

Related Questions