如何在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 $mailsdoecho "> [$addr]"done

输出:

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

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

RE:IFS解决方案,我试过这个,它的工作原理,我保留旧的IFS,然后恢复它:

IN="bla@some.com;john@home.com"
OIFS=$IFSIFS=';'mails2=$INfor x in $mails2doecho "> [$x]"done
IFS=$OIFS

顺便说一句当我试着

mails2=($IN)

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

3129906 次浏览

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

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

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

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

你可以设置内场分离器(IFS)变量,然后让它解析成一个数组。当这发生在命令中时,那么对IFS的赋值只发生在该单个命令的环境中(对read)。然后它根据IFS变量值将输入解析成一个数组,然后我们可以迭代它。

此示例将解析由;分隔的一行项,将其推送到一个数组中:

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

另一个示例用于处理$IN的整个内容,每次一行输入由;分隔:

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

这种方法怎么样:

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

来源

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

IFS=';' read ADDR1 ADDR2 <<<$IN

取自Bash外壳脚本拆分数组

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;

Darron的回答的不同之处,我是这样做的:

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

这是最简单的方法。

spo='one;two;three'OIFS=$IFSIFS=';'spo_array=($spo)IFS=$OIFSecho ${spo_array[*]}

有两个简单的方法:

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

cat "text1;text2;text3" | sed -e 's/ /\n/g'

将由';'分隔的字符串拆分为数组的单行代码是:

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

这只会在子shell中设置IFS,因此您不必担心保存和恢复其值。

这也有效:

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。

这里有一些很酷的答案(尤其是勘误器),但是对于类似于在其他语言中拆分的东西-这就是我最初的问题的意思-我解决了这个问题:

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

我认为AWK是解决问题的最佳有效命令。AWK默认包含在几乎所有Linux发行版中。

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

会给

bla@some.com john@home.com

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

相容的答案

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

但是,重要的是首先要注意bash有许多特别功能(所谓的),这些功能在任何其他中都不起作用。

特别是,数组关联数组模式替换,在这篇文章的解决方案中以及线程中的其他解决方案中使用,是巴什主义,可能在许多人使用的其他外壳下不起作用。

例如:在我的Debian GNU/Linux上,有一个名为9;破折号&9;" rel="tag">破折号的标准 shell;我知道很多人喜欢使用另一个名为的shell;还有一个名为的特殊工具,带有他自己的shell解释器()。

对于兼容的答案,请转到此答案的最后一部分!

请求参数

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

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

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

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

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

bash中,我们可以创建一个阵列,其中元素由的临时值拆分(输入字段分隔符)。IFS告诉bash在定义数组时应该将哪些字符视为元素之间的分隔符:

IN="bla@some.com;john@home.com;Full Name <fulnam@other.org>"
# save original IFS value so we can restore it lateroIFS="$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 argumentfor x in "${fields[@]}" ;doecho "> [$x]"done# > [bla@some.com]# > [john@home.com]# > [Full Name <fulnam@other.org>]

或者你可以使用变化方法处理后从数组中删除每个字段,我喜欢:

while [ "$fields" ] ;doecho "> [$fields]"# slice the arrayfields=("${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/bodyprintf "\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/bodyprintf "\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中使用的东西,你经常使用不能--这包括我们在上面的解决方案中使用的数组。

但是,我们不需要使用数组来遍历字符串的“元素”。许多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所示:

#%分别从字符串的开始结束中删除最短的匹配子字符串,并且

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

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

下面的代码块在9;bash&9;" rel="tag">bash(包括Mac OS的bash)、9;破折号&9;" rel="tag">破折号、中运行良好:

(感谢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".printf '> [%s]\n' "$iter"done# > [bla@some.com]# > [john@home.com]# > [Full Name <fulnam@other.org>]

为什么不cut

cut对于提取大文件中的列很有用,但是重复做叉子var=$(echo ... | cut ...))很快就会变得矫枉过正!

这是一个正确的语法,在许多9;posx&道格的另一个回答9;" rel="tag">posx9;shell&道格的另一个回答9;" rel="tag">shell下使用cut进行了测试,正如道格的另一个回答所建议的那样:

IN="bla@some.com;john@home.com;Full Name <fulnam@other.org>"i=1while iter=$(echo "$IN"|cut -d\; -f$i) ; [ -n "$iter" ] ;doprintf '> [%s]\n' "$iter"i=$((i+1))done

我写这个是为了比较执行时间

在我的raspberrypi上,这看起来像:

$ export TIMEFORMAT=$'(%U + %S) / \e[1m%R\e[0m : %P  '$ time sh splitDemo.sh >/dev/null(0.000 + 0.019) / 0.019 : 99.63$ time sh splitDemo_cut.sh >/dev/null(0.051 + 0.041) / 0.188 : 48.98

其中整体执行时间长了10倍,使用1叉子cut按外地

如果没有空间,为什么没有这个?

IN="bla@some.com;john@home.com"arr=(`echo $IN | tr ';' ' '`)
echo ${arr[0]}echo ${arr[1]}

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

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

然后,让派对开始:

echo $#for a; do echo $a; doneADDR1=$1 ADDR2=$2

两个不需要bash数组的波恩式替代方案:

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

IN="bla@some.comjohn@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"

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

在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 arraydeclare -a array='([0]="one" [1]="two three" [2]="*" [3]="there isa newlinein this field")'

这样做的诀窍是使用read(分隔符)的-d选项和一个空分隔符,这样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 arraydeclare -a array='([0]="one" [1]="two" [2]="three" [3]="")'

保留尾随的空字段。


Bash≥4.4更新

从Bash 4.4开始,内置mapfile(又名readarray)支持-d选项来指定分隔符。因此,另一种规范方式是:

mapfile -d ';' -t array < <(printf '%s;' "$in")
IN='bla@some.com;john@home.com;Charlie Brown <cbrown@acme.com;!"#$%&/()[]{}*? are no problem;simple is beautiful :-)'set -foldifs="$IFS"IFS=';'; arrayIN=($IN)IFS="$oldifs"for i in "${arrayIN[@]}"; doecho "$i"doneset +f

输出:

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

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

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

除了已经提供的精彩答案之外,如果只是打印出数据的问题,您可以考虑使用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]

在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

其中//表示全局替换。

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

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

示例:

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

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

当您有一个分隔的日志文件,其中包含以下行时,这会更有用:

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

cut非常方便,能够cat这个文件并选择一个特定的字段进行进一步处理。

这是一个干净的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

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

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 {}

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

IN="bla@so me.com;*;john@home.com"for i in `delims=${IN//[^;]}; seq 1 $((${#delims} + 1))`doecho "> [`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例如;;;。然后它从1循环到number-of-delimiters,由${#delims}计数。最后一步是使用cut安全地获得$i部分。

这就是我的答案!

DELIMITER_VAL='='
read -d '' F_ABOUT_DISTRO_R <<"EOF"DISTRIB_ID=UbuntuDISTRIB_RELEASE=14.04DISTRIB_CODENAME=trustyDISTRIB_DESCRIPTION="Ubuntu 14.04.4 LTS"NAME="Ubuntu"VERSION="14.04.4 LTS, Trusty Tahr"ID=ubuntuID_LIKE=debianPRETTY_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; doSPLIT+=("$line")done <<< "$SPLIT_NOW"for i in "${SPLIT[@]}"; doecho "$i"done

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

因为两个原因:

  1. 您执行不需要逃跑分隔符;
  2. 您将没有空格问题。该值将在数组中正确分隔。

不设置IFS

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

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

您将获得:

b = fooc = bar

这对我有效:

string="1;2"echo $string | cut -d';' -f1 # output is 1echo $string | cut -d';' -f2 # output is 2
IN="bla@some.com;john@home.com"IFS=';'read -a IN_arr <<< "${IN}"for entry in "${IN_arr[@]}"doecho $entrydone

产出

bla@some.comjohn@home.com

系统:Ubuntu 12.04.1

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

split() {local string="$1"local delimiter="$2"if [ -n "$string" ]; thenlocal partwhile read -d "$delimiter" part; doecho $partdone <<< "$string"echo $partfi}

例如,命令

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

产生

abc

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

$ split 'a;b;c' ';' | cat -n1   a2   b3   c

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

  • IFS未被覆盖:由于甚至局部变量的动态范围,在循环上覆盖IFS会导致新值泄漏到从循环内执行的函数调用中。

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

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

#!/usr/bin/env bash
split() {# ...}
split "$@"

您可以将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"

另一个迟到的答案……如果你是Java思维,这里是bashjhttps://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 truedoNAME=j.next()     # java method callif [ $NAME != null ] ; then echo $NAME ; else exit ; fidone

编辑:对不起,我在SO的某个地方读到POSIX需要perl,所以我认为使用它是合法的。但在unix.stackexchange.com,一些用户表示perl不是POSIX规范的一部分。

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

详细评论:

#!/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=$1local string=$2local -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++]=$elementdone <<<$(_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 <<-ENDmy \$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 <<-ENDmy @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=$1local string=$2local -n array=$3
local element i=0
local IFS=$'\n'
while read element; doarray[i++]=$elementdone <<<$(_perl_split)}
function _perl_split\{\{cat <<-ENDmy \$elementSepRE=q($elementSepRE);END
[[ $elementSepRE =~ ^m?/ && $elementSepRE =~ /[msixpodualn]*$ ]] && cat <<-END\$elementSepRE=qr$elementSepRE;END
cat <<-ENDmy @array=split(\$elementSepRE, q($string));
print(\$_ . "\\n") for (@array);END} | perl}
ADDR1=${IN%%;*}ADDR2=${IN##*;}

这么多的答案和这么多的复杂性。尝试一个更简单的解决方案:

echo "string1, string2" | tr , "\n"

tr(读取,翻译)将输入中的第一个参数替换为第二个参数。

所以tr,“\n”将逗号替换为输入中的新行字符,它变成:

string1string2