如何在 Bash 中的分隔符上拆分字符串?

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

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

现在我想用拆分字符串;分隔符,以便我有:

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

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


经过以下答案的建议后,我得出了以下结论:

#!/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重置为默认值?

RE: 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括弧,它可以工作。

答案

您可以设置内部字段分隔符 (IFS)变量,然后将其解析为数组。当这在命令中发生时,则仅向该单个命令的环境分配IFS (以read )。然后,它根据IFS变量值将输入解析为一个数组,然后可以对其进行迭代。

IFS=';' read -ra ADDR <<< "$IN"
for i in "${ADDR[@]}"; do
    # process "$i"
done

它将解析由;分隔的一行项目; ,将其推入数组。用于处理整个$IN ,每次输入一行用分隔;

while IFS=';' read -ra ADDR; do
      for i in "${ADDR[@]}"; do
          # process "$i"
      done
 done <<< "$IN"

取自Bash shell 脚本 split array

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

说明:

此构造替换了所有出现的';' (最初的//表示全局替换),在字符串IN使用' ' (单个空格),然后将以空格分隔的字符串解释为数组(这是括号内的内容)。

花括号内部用来替换每个';'的语法带有' '字符的字符称为参数扩展

有一些常见的陷阱:

  1. 如果原始字符串中有空格,则需要使用IFS
    • IFS=':'; arrIN=($IN); unset IFS;
  2. 如果原始字符串包含空格并且定界符是换行符,则可以使用以下命令设置IFS
    • IFS=$'\n'; arrIN=($IN); unset IFS;

如果您不介意立即处理它们,我喜欢这样做:

for i in $(echo $IN | tr ";" "\n")
do
  # process
done

您可以使用这种循环来初始化数组,但是可能有一种更简单的方法来执行此操作。希望这会有所帮助。

兼容答案

对于这个 SO 问题,在已经有很多不同的方法可以做到这一点。但是 bash 具有许多特殊功能,即所谓的bashism可以很好地工作,但是不能在任何其他

特别是, 数组关联数组模式替换是纯bashism ,在其他shell下可能不起作用。

在我的Debian GNU / Linux 上 ,有一个称为标准外壳,但是我知道很多人喜欢使用

最后,在很小的情况下,有一个名为的特殊工具,带有他自己的外壳解释器( )。

要求的字串

SO 问题中的字符串示例是:

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

由于这可能对空格有用,并且因为空格可以修改例程的结果,所以我更喜欢使用以下示例字符串:

IN="bla@some.com;john@home.com;Full Name <fulnam@other.org>"

根据定界符分割字符串(版本 > = 4.2)

bash 下,我们可以使用数组IFS

var="bla@some.com;john@home.com;Full Name <fulnam@other.org>"

oIFS="$IFS"
IFS=";"
declare -a fields=($var)
IFS="$oIFS"
unset oIFS

IFS=\; read -a fields <<<"$IN"

在最近的 bash 下使用此语法不会为当前会话更改$IFS ,而仅会为当前命令更改:

set | grep ^IFS=
IFS=$' \t\n'

现在,将字符串var拆分并存储到一个数组中(名为fields ):

set | grep ^fields=\\\|^var=
fields=([0]="bla@some.com" [1]="john@home.com" [2]="Full Name <fulnam@other.org>")
var='bla@some.com;john@home.com;Full Name <fulnam@other.org>'

我们可以使用declare -p请求变量内容:

declare -p IN fields
declare -- IN="bla@some.com;john@home.com;Full Name <fulnam@other.org>"
declare -a fields=([0]="bla@some.com" [1]="john@home.com" [2]="Full Name <fulnam@other.org>")

read是进行拆分的最快方法,因为没有分叉 ,也没有调用任何外部资源。

从那里,您可以使用已经知道的语法来处理每个字段:

for x in "${fields[@]}";do
    echo "> [$x]"
    done
> [bla@some.com]
> [john@home.com]
> [Full Name <fulnam@other.org>]

或在处理后删除每个字段(我喜欢这种转换方法):

while [ "$fields" ] ;do
    echo "> [$fields]"
    fields=("${fields[@]:1}")
    done
> [bla@some.com]
> [john@home.com]
> [Full Name <fulnam@other.org>]

甚至是简单的打印输出(较短的语法):

printf "> [%s]\n" "${fields[@]}"
> [bla@some.com]
> [john@home.com]
> [Full Name <fulnam@other.org>]

更新:最近 > = 4.4

你可以玩mapfile

mapfile -td \; fields < <(printf "%s\0" "$IN")

此语法保留特殊字符,换行符和空字段!

如果您不关心空字段,则可以:

mapfile -td \; fields <<<"$IN"
fields=("${fields[@]%$'\n'}")   # drop '\n' added by '<<<'

但是您可以通过函数使用字段:

myPubliMail() {
    printf "Seq: %6d: Sending mail to '%s'..." $1 "$2"
    # mail -s "This is not a spam..." "$2" </path/to/body
    printf "\e[3D, done.\n"
}

mapfile < <(printf "%s\0" "$IN") -td \; -c 1 -C myPubliMail

(注意:格式字符串末尾的\0无效,而您不必关心字符串末尾的空字段)

mapfile < <(echo -n "$IN") -td \; -c 1 -C myPubliMail

将呈现如下内容:

Seq:      0: Sending mail to 'bla@some.com', done.
Seq:      1: Sending mail to 'john@home.com', done.
Seq:      2: Sending mail to 'Full Name <fulnam@other.org>', done.

或者在函数中通过<<< bash 语法添加的 Drop newline:

myPubliMail() {
    local seq=$1 dest="${2%$'\n'}"
    printf "Seq: %6d: Sending mail to '%s'..." $seq "$dest"
    # mail -s "This is not a spam..." "$dest" </path/to/body
    printf "\e[3D, done.\n"
}

mapfile <<<"$IN" -td \; -c 1 -C myPubliMail

将呈现相同的输出:

Seq:      0: Sending mail to 'bla@some.com', done.
Seq:      1: Sending mail to 'john@home.com', done.
Seq:      2: Sending mail to 'Full Name <fulnam@other.org>', done.

基于分隔符分割字符串

但是,如果您要编写可在许多 shell 下使用的东西,则不必使用bashisms

在许多 Shell 中都有一种语法,用于在子字符串的第一次最后一次出现时将字符串拆分:

${var#*SubStr}  # will drop begin of string up to first occur of `SubStr`
${var##*SubStr} # will drop begin of string up to last occur of `SubStr`
${var%SubStr*}  # will drop part of string from last occur of `SubStr` to the end
${var%%SubStr*} # will drop part of string from first occur of `SubStr` to the end

(缺少此内容是我的答案发布的主要原因;)

Score_Under指出:

#%删除最短的匹配字符串,然后

##%%删除最长的时间。

其中###表示字符串的左边 (开始)开始,以及

%%%表示字符串的右边 (结尾)开始。

这个小示例脚本在下运行良好,并且也在 Mac-OS 的 bash 下进行了测试:

var="bla@some.com;john@home.com;Full Name <fulnam@other.org>"
while [ "$var" ] ;do
    iter=${var%%;*}
    echo "> [$iter]"
    [ "$var" = "$iter" ] && \
        var='' || \
        var="${var#*;}"
  done
> [bla@some.com]
> [john@home.com]
> [Full Name <fulnam@other.org>]

玩得开心!

我看到了几个参考cut命令的答案,但它们都已被删除。没有人详细说明这一点有点奇怪,因为我认为这是执行此类操作的更有用的命令之一,尤其是对于解析分隔的日志文件。

在将这个特定示例拆分为 bash 脚本数组的情况下, tr可能会更有效,但是可以使用cut ,并且如果您想从中间提取特定字段,则更有效。

例:

$ echo "bla@some.com;john@home.com" | cut -d ";" -f 1
bla@some.com
$ echo "bla@some.com;john@home.com" | cut -d ";" -f 2
john@home.com

您显然可以将其放入循环,并迭代 - f 参数以独立提取每个字段。

当您有一个带有行的定界日志文件时,这将变得更加有用:

2015-04-27|12345|some action|an attribute|meta data

cut是非常方便的能够cat这个文件,并选择用于进一步处理的特定领域。

这为我工作:

string="1;2"
echo $string | cut -d';' -f1 # output is 1
echo $string | cut -d';' -f2 # output is 2

这种方法怎么样:

IN="bla@some.com;john@home.com" 
set -- "$IN" 
IFS=";"; declare -a Array=($*) 
echo "${Array[@]}" 
echo "${Array[0]}" 
echo "${Array[1]}"

资源

echo "bla@some.com;john@home.com" | sed -e 's/;/\n/g'
bla@some.com
john@home.com

这也适用:

IN="bla@some.com;john@home.com"
echo ADD1=`echo $IN | cut -d \; -f 1`
echo ADD2=`echo $IN | cut -d \; -f 2`

请注意,此解决方案并不总是正确的。如果仅传递 “bla@some.com”,它将把它分配给 ADD1 和 ADD2。

我认为AWK是解决您问题的最佳且有效的命令。默认情况下,几乎每个 Linux 发行版都包含 AWK。

echo "bla@some.com;john@home.com" | awk -F';' '{print $1,$2}'

会给

bla@some.com john@home.com

当然,您可以通过重新定义 awk 打印字段来存储每个电子邮件地址。