2501

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

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

现在我想用;分隔符分割字符串,这样我就有:

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

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


根据以下答案的建议,我最终得到了以下结果:

#!/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

4

34 回答 34

1491

您可以设置内部字段分隔符(IFS) 变量,然后让它解析成一个数组。当这发生在命令中时,分配 toIFS只发生在该单个命令的环境 (to 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"
于 2009-05-28T02:23:27.017 回答
1307

取自Bash shell 脚本拆分数组

IN="bla@some.com;john@home.com"
arrIN=(${IN//;/ })
echo ${arrIN[1]}                  # Output: john@home.com

解释:

此构造用(单个空格)替换字符串中所有出现的';'(初始//表示全局替换),然后将空格分隔的字符串解释为数组(这就是周围括号的作用)。IN' '

花括号内用于将每个';'字符替换为' '字符的语法称为参数扩展

有一些常见的陷阱:

  1. 如果原始字符串有空格,则需要使用IFS
  • IFS=':'; arrIN=($IN); unset IFS;
  1. 如果原始字符串有空格并且分隔符是新行,则可以使用以下方式设置IFS
  • IFS=$'\n'; arrIN=($IN); unset IFS;
于 2011-03-10T09:00:43.977 回答
321

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

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

您可以使用这种循环来初始化数组,但可能有更简单的方法来完成它。

于 2009-05-28T02:09:44.237 回答
294

兼容的答案

中有很多不同的方法可以做到这一点。

但是,重要的是首先要注意它bash有许多在任何其他特殊功能(所谓的bashisms ) 。

特别是,本文中的解决方案以及线程中的其他解决方案中使用的数组关联数组模式替换是bashism,可能无法在许多人使用的其他shell下工作。

例如:在我的Debian GNU/Linux 上,有一个名为标准shell ;我知道很多人喜欢使用另一个叫做的 shell ;还有一个叫做的特殊工具,带有他自己的shell解释器()。

请求的字符串

上述问题中要拆分的字符串是:

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

我将使用此字符串的修改版本来确保我的解决方案对包含空格的字符串具有鲁棒性,这可能会破坏其他解决方案:

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

中的分隔符拆分字符串(版本 >=4.2)

pure bash中,我们可以创建一个数组,其中元素被IFS的临时值(输入字段分隔符)分割。除其他外,IFS 告诉bash定义数组时应将哪些字符视为元素之间的分隔符:

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

# save original IFS value so we can restore it later
oIFS="$IFS"
IFS=";"
declare -a fields=($IN)
IFS="$oIFS"
unset oIFS

在较新的版本中bash,使用 IFS 定义为命令添加前缀只会更改该命令的 IFS,然后立即将其重置为以前的值。这意味着我们可以在一行中完成上述操作:

IFS=\; read -a fields <<<"$IN"
# after this command, the IFS resets back to its previous value (here, the default):
set | grep ^IFS=
# IFS=$' \t\n'

我们可以看到该字符串IN已存储到一个名为 的数组fields中,并以分号分隔:

set | grep ^fields=\\\|^IN=
# fields=([0]="bla@some.com" [1]="john@home.com" [2]="Full Name <fulnam@other.org>")
# IN='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是进行拆分的最快方法,因为没有调用分叉或外部资源。

定义数组后,您可以使用一个简单的循环来处理每个字段(或者,更确切地说,处理您现在定义的数组中的每个元素):

# `"${fields[@]}"` expands to return every element of `fields` array as a separate argument
for x in "${fields[@]}" ;do
    echo "> [$x]"
    done
# > [bla@some.com]
# > [john@home.com]
# > [Full Name <fulnam@other.org>]

或者,您可以在使用移位方法处理后从数组中删除每个字段,我喜欢这种方法:

while [ "$fields" ] ;do
    echo "> [$fields]"
    # slice the array 
    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

在较新的版本中bash,您还可以使用以下命令mapfile

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

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

如果您不想包含空字段,可以执行以下操作:

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

使用mapfile,您还可以跳过声明数组并隐式“循环”分隔元素,在每个元素上调用一个函数:

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.

或者您可以使用<<<, 并在函数体中包含一些处理以删除它添加的换行符:

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

# Renders the same output:
# 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,或者如果你想写一些可以在许多不同的 shell 中使用的东西,你通常不能使用bashisms——这包括我们在上面的解决方案中一直使用的数组。

但是,我们不需要使用数组来循环字符串的“元素”。在许多 shell 中都有一种语法用于从模式的第一次最后一次出现中删除字符串的子字符串。请注意,这*是一个代表零个或多个字符的通配符:

(到目前为止发布的任何解决方案都缺乏这种方法是我写这个答案的主要原因;)

${var#*SubStr}  # drops substring from start of string up to first occurrence of `SubStr`
${var##*SubStr} # drops substring from start of string up to last occurrence of `SubStr`
${var%SubStr*}  # drops substring from last occurrence of `SubStr` to end of string
${var%%SubStr*} # drops substring from first occurrence of `SubStr` to end of string

正如Score_Under所解释的:

#并分别从字符串的开头结尾%删除可能的最短匹配子字符串,并且

##%%删除可能最长的匹配子字符串。

使用上述语法,我们可以创建一种方法,通过删除分隔符之前或之后的子字符串,从字符串中提取子字符串“元素”。

下面的代码块在(包括 Mac OS 的bash)、中运行良好:

(感谢Adam Katz评论,让这个循环变得更加简单!)

IN="bla@some.com;john@home.com;Full Name <fulnam@other.org>"
while [ "$IN" != "$iter" ] ;do
    # extract the substring from start of string up to delimiter.
    iter=${IN%%;*}
    # delete this first "element" AND next separator, from $IN.
    IN="${IN#$iter;}"
    # Print (or doing anything with) the first "element".
    echo "> [$iter]"
done
# > [bla@some.com]
# > [john@home.com]
# > [Full Name <fulnam@other.org>]

玩得开心!

于 2013-04-13T14:20:09.927 回答
288

我已经看到几个引用该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对该文件并选择特定字段进行进一步处理非常方便。

于 2015-04-27T18:20:34.580 回答
168

这对我有用:

string="1;2"
echo $string | cut -d';' -f1 # output is 1
echo $string | cut -d';' -f2 # output is 2
于 2016-08-11T20:45:25.073 回答
136

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

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

会给

bla@some.com john@home.com

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

于 2013-01-14T06:33:40.037 回答
100

这种方法怎么样:

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

来源

于 2009-05-28T10:31:16.910 回答
81
echo "bla@some.com;john@home.com" | sed -e 's/;/\n/g'
bla@some.com
john@home.com
于 2009-05-28T02:12:59.050 回答
71

这也有效:

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。

于 2012-09-08T05:01:42.753 回答
39

在 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")'

这个工作的诀窍是使用 (delimiter) 的-d选项read和一个空的分隔符,所以它read被迫读取它输入的所有内容。并且我们read准确地提供了变量的内容in,由于没有尾随换行符printf。请注意,我们还将分隔符放入printf以确保传递给的字符串read具有尾随分隔符。没有它,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 开始,内置函数mapfile(aka readarray) 支持-d指定分隔符的选项。因此,另一种规范的方式是:

mapfile -d ';' -t array < <(printf '%s;' "$in")
于 2014-06-26T09:11:21.740 回答
38

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

IN="bla@some.com;john@home.com"
read ADDR1 ADDR2 <<<$(IFS=";"; echo $IN)
于 2011-07-05T13:41:34.493 回答
37

如果您不使用数组,这一个衬里怎么样:

IFS=';' read ADDR1 ADDR2 <<<$IN
于 2010-09-13T20:10:42.723 回答
32

不设置 IFS

如果你只有一个冒号,你可以这样做:

a="foo:bar"
b=${a%:*}
c=${a##*:}

你会得到:

b = foo
c = bar
于 2016-08-01T13:15:07.147 回答
23

这是一个干净的 3 衬里:

in="foo@bar;bizz@buzz;fizz@buzz;buzz@woof"
IFS=';' list=($in)
for item in "${list[@]}"; do echo $item; done

其中IFS基于分隔符分隔单词并()用于创建数组。然后[@]用于将每个项目作为单独的单词返回。

如果之后有任何代码,还需要恢复$IFS,例如unset IFS.

于 2015-09-11T20:54:49.903 回答
14

以下 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循环会导致新值泄漏到循环内执行的函数调用中。

  • 不使用数组:使用将字符串读入数组需要Bash 和zsh中read的标志。-a-A

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

#!/usr/bin/env bash

split() {
    # ...
}

split "$@"
于 2017-05-24T08:42:53.397 回答
11

您可以将 awk 应用于许多情况

echo "bla@some.com;john@home.com"|awk -F';' '{printf "%s\n%s\n", $1, $2}'

你也可以用这个

echo "bla@some.com;john@home.com"|awk -F';' '{print $1,$2}' OFS="\n"
于 2018-01-20T15:54:29.427 回答
9

有一个简单而聪明的方法是这样的:

echo "add:sfff" | xargs -d: -i  echo {}

但是你必须使用 gnu xargs,BSD xargs 不支持 -d delim。如果你像我一样使用苹果mac。您可以安装 gnu xargs :

brew install findutils

然后

echo "add:sfff" | gxargs -d: -i  echo {}
于 2015-09-16T03:34:51.353 回答
4

这是最简单的方法。

spo='one;two;three'
OIFS=$IFS
IFS=';'
spo_array=($spo)
IFS=$OIFS
echo ${spo_array[*]}
于 2011-09-25T01:09:38.887 回答
4

这里有一些很酷的答案(尤其是错误的),但是对于类似于在其他语言中拆分的东西——这就是我认为原始问题的意思——我决定了这一点:

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解决方案。

于 2012-10-22T07:10:09.673 回答
4

如果没有空间,为什么不呢?

IN="bla@some.com;john@home.com"
arr=(`echo $IN | tr ';' ' '`)

echo ${arr[0]}
echo ${arr[1]}
于 2013-04-24T13:13:57.663 回答
3
IN="bla@some.com;john@home.com"
IFS=';'
read -a IN_arr <<< "${IN}"
for entry in "${IN_arr[@]}"
do
    echo $entry
done

输出

bla@some.com
john@home.com

系统:Ubuntu 12.04.1

于 2016-10-25T12:41:51.933 回答
2

使用set内置加载$@数组:

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

然后,让派对开始:

echo $#
for a; do echo $a; done
ADDR1=$1 ADDR2=$2
于 2013-04-30T03:10:43.010 回答
2

两个都不需要 bash 数组的 bourne-ish 替代方案:

案例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"

在这两种情况下,可以在循环内组成的子列表在循环完成后是持久的。这在操作内存中的列表时很有用,而不是将列表存储在文件中。{ps保持冷静并继续B-)}

于 2013-09-02T06:30:53.097 回答
2
IN='bla@some.com;john@home.com;Charlie Brown <cbrown@acme.com;!"#$%&/()[]{}*? are no problem;simple is beautiful :-)'
set -f
oldifs="$IFS"
IFS=';'; arrayIN=($IN)
IFS="$oldifs"
for i in "${arrayIN[@]}"; do
echo "$i"
done
set +f

输出:

bla@some.com
john@home.com
Charlie Brown <cbrown@acme.com
!"#$%&/()[]{}*? are no problem
simple is beautiful :-)

说明:使用括号 () 的简单赋值将分号分隔的列表转换为数组,前提是您在执行此操作时具有正确的 IFS。标准 FOR 循环像往常一样处理该数组中的各个项目。请注意,为 IN 变量提供的列表必须是“硬”引用的,即带有单个刻度。

IFS 必须被保存和恢复,因为 Bash 不会以与命令相同的方式处理分配。另一种解决方法是将赋值包装在一个函数中,并使用修改后的 IFS 调用该函数。在这种情况下,不需要单独保存/恢复 IFS。感谢“Bize”指出这一点。

于 2014-10-10T11:33:54.217 回答
2

除了已经提供的精彩答案之外,如果只是打印出您可能考虑使用的数据awk

awk -F";" '{for (i=1;i<=NF;i++) printf("> [%s]\n", $i)}' <<< "$IN"

这会将字段分隔符设置为;,以便它可以通过循环遍历字段for并相应地打印。

测试

$ IN="bla@some.com;john@home.com"
$ awk -F";" '{for (i=1;i<=NF;i++) printf("> [%s]\n", $i)}' <<< "$IN"
> [bla@some.com]
> [john@home.com]

使用另一个输入:

$ awk -F";" '{for (i=1;i<=NF;i++) printf("> [%s]\n", $i)}' <<< "a;b;c   d;e_;f"
> [a]
> [b]
> [c   d]
> [e_]
> [f]
于 2015-01-08T10:21:45.897 回答
2

在 Android shell 中,大多数建议的方法都不起作用:

$ IFS=':' read -ra ADDR <<<"$PATH"                             
/system/bin/sh: can't create temporary file /sqlite_stmt_journals/mksh.EbNoR10629: No such file or directory

起作用的是:

$ for i in ${PATH//:/ }; do echo $i; done
/sbin
/vendor/bin
/system/sbin
/system/bin
/system/xbin

where//表示全局替换。

于 2015-02-20T10:49:59.877 回答
2

好吧,伙计们!

这是我的答案!

DELIMITER_VAL='='

read -d '' F_ABOUT_DISTRO_R <<"EOF"
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=14.04
DISTRIB_CODENAME=trusty
DISTRIB_DESCRIPTION="Ubuntu 14.04.4 LTS"
NAME="Ubuntu"
VERSION="14.04.4 LTS, Trusty Tahr"
ID=ubuntu
ID_LIKE=debian
PRETTY_NAME="Ubuntu 14.04.4 LTS"
VERSION_ID="14.04"
HOME_URL="http://www.ubuntu.com/"
SUPPORT_URL="http://help.ubuntu.com/"
BUG_REPORT_URL="http://bugs.launchpad.net/ubuntu/"
EOF

SPLIT_NOW=$(awk -F$DELIMITER_VAL '{for(i=1;i<=NF;i++){printf "%s\n", $i}}' <<<"${F_ABOUT_DISTRO_R}")
while read -r line; do
   SPLIT+=("$line")
done <<< "$SPLIT_NOW"
for i in "${SPLIT[@]}"; do
    echo "$i"
done

为什么这种方法对我来说是“最好的”?

因为两个原因:

  1. 不需要转义分隔符;
  2. 你不会有空格的问题。该值将在数组中正确分隔!

[]的

于 2016-04-04T19:54:13.807 回答
1

用于拆分由 ';' 分隔的字符串的单行 进入一个数组是:

IN="bla@some.com;john@home.com"
ADDRS=( $(IFS=";" echo "$IN") )
echo ${ADDRS[0]}
echo ${ADDRS[1]}

这仅将 IFS 设置在子外壳中,因此您不必担心保存和恢复其值。

于 2012-06-14T17:38:03.537 回答
0

也许不是最优雅的解决方案,但适用于*和空格:

IN="bla@so me.com;*;john@home.com"
for i in `delims=${IN//[^;]}; seq 1 $((${#delims} + 1))`
do
   echo "> [`echo $IN | cut -d';' -f$i`]"
done

输出

> [bla@so me.com]
> [*]
> [john@home.com]

其他示例(开头和结尾的分隔符):

IN=";bla@so me.com;*;john@home.com;"
> []
> [bla@so me.com]
> [*]
> [john@home.com]
> []

基本上它会删除除;制作之外的所有字符delims;;;. 然后它确实从到for循环,由计数。最后一步是使用.1number-of-delimiters${#delims}$icut

于 2016-02-26T12:20:31.403 回答
0
ADDR1=${IN%%;*}
ADDR2=${IN##*;}
于 2022-02-28T14:29:01.590 回答
-1

编辑: 对不起,我在某处读过perlPOSIX 要求的 SO,所以我认为使用它是合法的。但是在 unix.stackexchange.com 上,一些用户状态perl不是 POSIX 规范的一部分。

我的解决方案:使用perl'ssplit来完成工作的函数。

附有详细评论:

#!/bin/bash

# This function is a wrapper for Perl's split.\
# \
# Since we cannot return an array like in Perl,
# it takes the name of the resulting array as last
# argument.\
# \
# See https://perldoc.perl.org/functions/split for usage info
# and examples.\
# \
# If you provide a Perl regexp that contains e. g. an escaped token like \b,
# space(s) and/or capture group(s), it must be quoted, and e. g. /\b/ must
# be single-quoted.\
# Thus, it's best to generally single-quote a Perl regexp.
function split # Args: <Element separator regexp> <string> <array name>
{
    (($# != 3)) && echo "${FUNCNAME[0]}: Wrong number of arguments, returning." && return 1

    local elementSepRE=$1
    local string=$2
    local -n array=$3

    local element i=0

    # Attention! read does Word Splitting on each line!
    # I must admit I didn't know that so far.
    # This removes leading and trailing spaces, exactly
    # what we don't want.
    # Thus, we set IFS locally to newline only.
    local IFS=$'\n'

    while read element; do
        # As opposed to array+=($element),
        # this preserves leading and trailing spaces.
        array[i++]=$element
    done <<<$(_perl_split)
}

# This function calls Perl's split function and prints the elements of the
# resulting array on separate lines.\
# It uses the caller's $elementSepRE and $string.
function _perl_split
{
    # A heredoc is a great way of embedding a Perl script.
    # N.B.: - Shell variables get expanded.
    #         - Thus:
    #           - They must be quoted.
    #           - Perl scalar variables must be escaped.
    #       - The backslash of \n must be escaped to protect it.
    #       - Instead of redirecting a single heredoc to perl, we may
    #         use multiple heredocs with cat within a command group and
    #         pipe the result to perl.
    #         This enables us to conditionally add certain lines of code.

    {
        cat <<-END
            my \$elementSepRE=q($elementSepRE);
        END

        # If $elementSepRE is a literal Perl regexp, qr must be applied
        # to it in order to use it.
        # N.B.: We cannot write this condition in Perl because when perl
        # compiles the script, all statements are checked for validity,
        # no matter if they will actually be executed or not.
        # And if $elementSepRE was e. g. == ', the line below – although
        # not to be executed – would give an error because of an unterminated
        # single-quoted string.
        [[ $elementSepRE =~ ^m?/ && $elementSepRE =~ /[msixpodualn]*$ ]] && cat <<-END
            \$elementSepRE=qr$elementSepRE;
        END

        cat <<-END
            my @array=split(\$elementSepRE, q($string));

            print(\$_ . "\\n") for (@array);
        END
    } | perl
}

对于那些一眼就知道发生了什么的人来说,同样没有评论;)

#!/bin/bash

# This function is a wrapper for Perl's split.\
# \
# Since we cannot return an array like in Perl,
# it takes the name of the resulting array as last
# argument.\
# \
# See https://perldoc.perl.org/functions/split for usage info
# and examples.\
# \
# If you provide a Perl regexp that contains e. g. an escaped token like \b,
# space(s) and/or capture group(s), it must be quoted, and e. g. /\b/ must
# be single-quoted.\
# Thus, it's best to generally single-quote a Perl regexp.
function split # Args: <Element separator regexp> <string> <array name>
{
    (($# != 3)) && echo "${FUNCNAME[0]}: Wrong number of arguments, returning." && return 1

    local elementSepRE=$1
    local string=$2
    local -n array=$3

    local element i=0

    local IFS=$'\n'

    while read element; do
        array[i++]=$element
    done <<<$(_perl_split)
}

function _perl_split
{
    {
        cat <<-END
            my \$elementSepRE=q($elementSepRE);
        END

        [[ $elementSepRE =~ ^m?/ && $elementSepRE =~ /[msixpodualn]*$ ]] && cat <<-END
            \$elementSepRE=qr$elementSepRE;
        END

        cat <<-END
            my @array=split(\$elementSepRE, q($string));

            print(\$_ . "\\n") for (@array);
        END
    } | perl
}
于 2021-08-22T01:15:32.580 回答
-8

有两种简单的方法:

cat "text1;text2;text3" | tr " " "\n"

cat "text1;text2;text3" | sed -e 's/ /\n/g'
于 2011-10-12T11:09:41.987 回答
-9

又一个迟到的答案...如果您有 Java 思维,这里是bashj ( https://sourceforge.net/projects/bashj/ ) 解决方案:

#!/usr/bin/bashj

#!java

private static String[] cuts;
private static int cnt=0;
public static void split(String words,String regexp) {cuts=words.split(regexp);}
public static String next() {return(cnt<cuts.length ? cuts[cnt++] : "null");}

#!bash

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

: j.split($IN,";")    # java method call

while true
do
    NAME=j.next()     # java method call
    if [ $NAME != null ] ; then echo $NAME ; else exit ; fi
done
于 2018-06-13T15:58:57.890 回答