我将此字符串存储在变量中:

IN="bla@some.com;john@home.com"

现在,我想将字符串拆分为:;分隔符,以便我可以:

ADDR1="bla@some.com"
ADDR2="john@home.com"

我不一定需要ADDR1和ADDR2变量。如果它们是数组的元素,那就更好了。


根据以下答案的建议,我最终得出了以下结论,这就是我想要的:

#!/usr/bin/env bash

IN="bla@some.com;john@home.com"

mails=$(echo $IN | tr ";" "\n")

for addr in $mails
do
    echo "> [$addr]"
done

输出:

> [bla@some.com]
> [john@home.com]

有一个解决方案涉及将Internal_field_separator(IFS)设置为;。我不确定这个答案发生了什么,如何将IFS重置为默认值?

回复:IFS解决方案,我尝试了这个,它奏效了,我保留了旧的IFS,然后恢复它:

IN="bla@some.com;john@home.com"

OIFS=$IFS
IFS=';'
mails2=$IN
for x in $mails2
do
    echo "> [$x]"
done

IFS=$OIFS

顺便说一下,当我尝试

mails2=($IN)

在循环中打印时,我只得到了第一个字符串,$in周围没有括号,它可以工作。


当前回答

在Bash中,这是一种防弹的方式,即使您的变量包含换行符,也可以使用:

IFS=';' read -d '' -ra array < <(printf '%s;\0' "$in")

看:

$ in=$'one;two three;*;there is\na newline\nin this field'
$ IFS=';' read -d '' -ra array < <(printf '%s;\0' "$in")
$ declare -p array
declare -a array='([0]="one" [1]="two three" [2]="*" [3]="there is
a newline
in this field")'

这项工作的诀窍是使用带有空分隔符的-d选项read(delimiter),这样read就被迫读取它所输入的所有内容。而且,由于printf,我们将read与中变量的内容完全匹配,没有换行符。注意,我们还在printf中放置分隔符,以确保传递给读取的字符串具有尾随分隔符。如果没有它,read将删除可能的尾随空字段:

$ in='one;two;three;'    # there's an empty field
$ IFS=';' read -d '' -ra array < <(printf '%s;\0' "$in")
$ declare -p array
declare -a array='([0]="one" [1]="two" [2]="three" [3]="")'

保留后面的空字段。


Bash≥4.4的更新

从Bash 4.4开始,内置映射文件(也称为readarray)支持-d选项来指定分隔符。因此,另一种规范方法是:

mapfile -d ';' -t array < <(printf '%s;' "$in")

其他回答

使用内置的集合加载$@数组:

IN="bla@some.com;john@home.com"
IFS=';'; set $IN; IFS=$' \t\n'

然后,让聚会开始:

echo $#
for a; do echo $a; done
ADDR1=$1 ADDR2=$2

对达伦的回答有不同的看法,我是这样做的:

IN="bla@some.com;john@home.com"
read ADDR1 ADDR2 <<<$(IFS=";"; echo $IN)

以下Bash/zsh函数将其第一个参数拆分为第二个参数给出的分隔符:

split() {
    local string="$1"
    local delimiter="$2"
    if [ -n "$string" ]; then
        local part
        while read -d "$delimiter" part; do
            echo $part
        done <<< "$string"
        echo $part
    fi
}

例如,命令

$ split 'a;b;c' ';'

产量

a
b
c

例如,该输出可以通过管道传输到其他命令。例子:

$ split 'a;b;c' ';' | cat -n
1   a
2   b
3   c

与给出的其他解决方案相比,该解决方案具有以下优点:

IFS没有被覆盖:由于甚至局部变量的动态作用域,在循环上覆盖IFS会导致新值泄漏到从循环内执行的函数调用中。不使用数组:使用read将字符串读入数组需要Bash中的-a标志和zsh中的-a标志。

如果需要,可以将函数放入脚本,如下所示:

#!/usr/bin/env bash

split() {
    # ...
}

split "$@"

两种都不需要bash数组的伯尔尼式替代方案:

案例1:保持简洁:使用NewLine作为记录分隔符。。。如。

IN="bla@some.com
john@home.com"

while read i; do
  # process "$i" ... eg.
    echo "[email:$i]"
done <<< "$IN"

注意:在第一种情况下,没有分支子进程来帮助列表操作。

想法:也许值得在内部广泛使用NL,在外部生成最终结果时只转换为不同的RS。

案例2:使用“;”作为记录分隔符。。。如。

NL="
" IRS=";" ORS=";"

conv_IRS() {
  exec tr "$1" "$NL"
}

conv_ORS() {
  exec tr "$NL" "$1"
}

IN="bla@some.com;john@home.com"
IN="$(conv_IRS ";" <<< "$IN")"

while read i; do
  # process "$i" ... eg.
    echo -n "[email:$i]$ORS"
done <<< "$IN"

在这两种情况下,子列表都可以在循环中组成。循环完成后,子列表是持久的。这在处理内存中的列表时非常有用,而不是将列表存储在文件中。{请保持冷静,继续进行B-)}

这里有一些很酷的答案(特别是勘误表),但对于类似于其他语言中的分裂的东西——这就是我最初的问题的意思——我决定了这个问题:

IN="bla@some.com;john@home.com"
declare -a a="(${IN/;/ })";

现在,${a[0]}、${a[1]}等都是您所期望的。使用${#a[*]}来表示术语的数量。或者迭代,当然:

for i in ${a[*]}; do echo $i; done

重要说明:

这在没有空间可担心的情况下有效,这解决了我的问题,但可能无法解决你的问题。在这种情况下,使用$IFS解决方案。