兼容答案
有很多不同的方法可以做到这一点 重击。
但是,重要的是要首先注意到它bash
具有许多其他功能无法使用的特殊功能(所谓的bashisms)贝壳。
特别是,本文中的解决方案以及线程中的其他解决方案中使用的array,关联数组和模式替换是一种bashisms,可能无法在许多人使用的其他shell下工作。
例如:在我的Debian GNU / Linux上,有一个标准叫壳短跑; 我知道很多人喜欢使用另一个称为sh; 还有一个叫做忙箱 用他自己的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
),短跑, sh和 忙箱的 灰:
IN="bla@some.com;john@home.com;Full Name <fulnam@other.org>"
while [ "$IN" ] ;do
# extract the substring from start of string up to delimiter.
# this is the first "element" of the string.
iter=${IN%%;*}
echo "> [$iter]"
# if there's only one element left, set `IN` to an empty string.
# this causes us to exit this `while` loop.
# else, we delete the first "element" of the string from IN, and move onto the next.
[ "$IN" = "$iter" ] && \
IN='' || \
IN="${IN#*;}"
done
# > [bla@some.com]
# > [john@home.com]
# > [Full Name <fulnam@other.org>]
玩得开心!