Bash数组的前缀和后缀元素

42

我希望在Bash中类似于花括号扩展一样,在数组前缀和后缀加上内容。

假设我有一个Bash数组:

ARRAY=( one two three )

我希望能够像以下花括号扩展那样,在前缀和后缀处进行操作

echo prefix_{one,two,three}_suffix

我能找到的最好方法是使用Bash正则表达式,可以添加前缀或后缀。

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

但我找不到如何同时执行这两个操作的任何信息。可能我可以使用正则表达式捕获并执行类似以下的操作:

但我无法以同时执行这两个操作的方式找到任何信息。有可能我可以使用正则表达式捕获,然后执行类似以下的操作:

echo ${ARRAY[@]/.*/prefix_$1_suffix}
但似乎bash变量的正则表达式替换不支持捕获,我也可以像这样存储一个临时的数组变量
PRE=(${ARRAY[@]/#/prefix_})
echo ${PRE[@]/%/_suffix}

这可能是我能想到的最好的方法,但它仍然似乎不够好。最后一个替代方案是使用类似于for循环的方法。

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

但那样看起来太丑了。而且我不知道如果我想在前缀、后缀或数组元素中任何地方添加空格,该怎么做才能让它正常工作。

6个回答

26

Bash花括号扩展不使用正则表达式。使用的模式只是一些shell glob,可以在bash手册 3.5.8.1模式匹配中找到。

您的两步解决方案很棒,但需要添加引号以确保空格安全:

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

您也可以用一些不太道德的方式来完成它:

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

注意:如果您设置了IFS=$'\n'(以处理包含空格的名称),则前缀命令将无法保留原始的数组结构。它只会让数组包含只有1个项目(一个字符串列表),其中包含所有更改后的字符串。如若运行后,可以看到:declare -p array。当运行后缀命令时,它只会将其添加到列表中的最后一个项目中。 - Magne
@Magne 不,它根本不依赖IFS。你从哪里得到这个想法的?试试这个:IFS='' ARRAY=(p $'\nq' 'r s'); ARR_PRE=("${ARRAY[@]/#/prefix_}"); ARR_POS=("${ARR_PRE[@]/%/_suffix}"); declare -p ARR_POS。甚至printf也不会出问题。 - Mingye Wang
如果在你的评论示例代码中设置IFS=$'\n',你就会明白我的意思。它输出:declare -a ARR_POS='([0]="prefix_p prefix_q prefix_r s_suffix")' - Magne
1
@Magne 真是糟糕透顶。IFS 为 \r\t\f 甚至 \a 时也会出现问题。这在 bash 3.2.57(1) (Apple) 上发生,但在 5.0.17(1) 上却没有。我认为这是一个旧的 bash bug,你刚好碰上了... - Mingye Wang

16

你可以使用以下代码以更友好的方式编写你的最后一个循环:

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

3

更美观,但与使用循环的解决方案本质上相同:

$ 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将行读入数组元素中。使用-d $'\0'代替会读取以null为分隔符的字符串,并且-t会省略结果中的分隔符。请参阅help mapfile了解更多信息。


mapfile -d '' 也会进行 NUL 终止。 - usretc
@usretc 是的,但 $'\0' 对读者更加易懂。 - Niklas Holm
是的。同样需要指出的是,-t并不是必需的,因为BASH会在变量中剥离NUL,但这只是一个产物。 - usretc

1

对于数组:

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

对于字符串:

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

eval会导致其参数被评估两次,在两种情况下第一次评估的结果都是相同的。

echo prefix_{one,two,three}_suffix

并且第二步执行它。对于数组情况,使用子shell来避免覆盖IFS。

在zsh中也可以这样做:

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

1
也许这是最优雅的解决方案:
$ 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
$

通过在数组重新分配之前使用IFS=$'\n'(仅对此分配行有效),可以保留前缀和后缀以及数组元素字符串中的空格。

使用"printf"非常方便,因为它允许将格式字符串(第一个参数)应用于提供给"printf"调用的每个附加字符串参数。


-1

我有完全相同的问题,并使用sed的单词边界匹配机制提出了以下解决方案:

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

等待更优雅的解决方案...


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接