我将此字符串存储在变量中:
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
重置为默认值?
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
括弧,它可以工作。
#1楼
单线分割用';'分隔的字符串 放入数组是:
IN="bla@some.com;john@home.com"
ADDRS=( $(IFS=";" echo "$IN") )
echo ${ADDRS[0]}
echo ${ADDRS[1]}
这仅将IFS设置在子外壳中,因此您不必担心保存和恢复其值。
#2楼
这也适用:
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。
#3楼
这里有一些很酷的答案(特别是错误),但是对于类似于其他语言的拆分来说,这就是我最初想表达的意思,我就此解决了:
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
解决方案。
#4楼
我认为AWK是解决您问题的最佳且有效的命令。 默认情况下,几乎每个Linux发行版都包含AWK。
echo "bla@some.com;john@home.com" | awk -F';' '{print $1,$2}'
会给
bla@some.com john@home.com
当然,您可以通过重新定义awk打印字段来存储每个电子邮件地址。
#5楼
兼容答案
对于这个SO问题,在bash中已经有很多不同的方法可以做到这一点。 但是bash具有许多特殊功能,即所谓的bashism可以很好地工作,但是不能在任何其他shell中工作 。
特别是, 数组 , 关联数组和模式替换是纯bashism ,在其他shell下可能不起作用。
在我的Debian GNU / Linux上 ,有一个称为dash的标准外壳,但是我知道很多人喜欢使用ksh 。
最后,在很小的情况下,有一个名为busybox的特殊工具,带有他自己的外壳解释器( ash )。
要求的字串
SO问题中的字符串示例是:
IN="bla@some.com;john@home.com"
由于这可能对空格有用,并且因为空格可以修改例程的结果,所以我更喜欢使用以下示例字符串:
IN="bla@some.com;john@home.com;Full Name <fulnam@other.org>"
根据bash中的定界符分割字符串(版本> = 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>]
更新:最近bash > = 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中基于分隔符分割字符串
但是,如果您要编写可在许多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指出:
#
和%
删除最短的匹配字符串,然后
##
和%%
删除最长的时间。其中
#
和##
表示从字符串的左边 (开始)开始,以及
%
和%%
表示从字符串的右边 (结尾)开始。
这个小示例脚本在bash , dash , ksh , busybox下运行良好,并且也在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>]
玩得开心!
#6楼
如果没有空间,为什么不这样做呢?
IN="bla@some.com;john@home.com"
arr=(`echo $IN | tr ';' ' '`)
echo ${arr[0]}
echo ${arr[1]}
#7楼
使用内置的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
#8楼
两个不需bash阵列的本地选择:
情况1 :保持简单易懂:使用NewLine作为Record-Separator...。
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-)}
#9楼
在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
与空分隔符(分隔符),让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的更新
由于击4.4,内建的mapfile
(又名readarray
)支持-d
选项来指定的分隔符。 因此,另一种规范的方式是:
mapfile -d ';' -t array < <(printf '%s;' "$in")
#10楼
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”指出这一点。
#11楼
除了已经提供的奇妙答案外,如果仅打印出数据,您可以考虑使用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]
#12楼
在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
//
表示全局替换。
#13楼
我看到了几个参考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
这个文件,并选择用于进一步处理的特定领域。
#14楼
这是干净的三层纸:
in="foo@bar;bizz@buzz;fizz@buzz;buzz@woof"
IFS=';' list=($in)
for item in "${list[@]}"; do echo $item; done
其中, IFS
基于分隔符和()
分隔单词以创建数组 。 然后,使用[@]
将每个项目作为一个单独的单词返回。
如果之后有任何代码,则还需要还原$IFS
,例如,未unset IFS
。
#15楼
有一种简单而聪明的方法,如下所示:
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 {}
#16楼
也许不是最优雅的解决方案,但可以使用*
和空格:
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
循环从1
到number-of-delimiters
由作为计数${#delims}
。 最后一步是使用cut
安全地获得第$i
个零件。
#17楼
大家好!
这是我的答案!
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
为什么这种方法对我来说是“最好的”?
有两个原因:
- 您无需转义分隔符;
- 您将不会有空格的问题 。 该值将在数组中正确分隔!
[]的
#18楼
如果您不使用数组,那么这种衬板怎么样:
IFS=';' read ADDR1 ADDR2 <<<$IN
#19楼
无需设置IFS
如果您只有一个冒号,您可以这样做:
a="foo:bar"
b=${a%:*}
c=${a##*:}
你会得到:
b = foo
c = bar
#20楼
这对我有用:
string="1;2"
echo $string | cut -d';' -f1 # output is 1
echo $string | cut -d';' -f2 # output is 2
#21楼
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
#22楼
以下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 "$@"
#23楼
您可以在许多情况下使用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"
#24楼
IN="bla@some.com;john@home.com"
arrIN=(${IN//;/ })
说明:
此构造替换了所有出现的';'
(最初的//
表示全局替换),在字符串IN
使用' '
(单个空格),然后将以空格分隔的字符串解释为数组(这是括号内的内容)。
花括号内部用来替换每个';'
的语法 带有' '
字符的字符称为参数扩展 。
有一些常见的陷阱:
- 如果原始字符串中有空格,则需要使用IFS :
-
IFS=':'; arrIN=($IN); unset IFS;
-
- 如果原始字符串包含空格并且定界符是换行符,则可以使用以下命令设置IFS :
-
IFS=$'\\n'; arrIN=($IN); unset IFS;
-
#25楼
对Darron的答案有不同的看法,这就是我的做法:
IN="bla@some.com;john@home.com"
read ADDR1 ADDR2 <<<$(IFS=";"; echo $IN)
#26楼
这是最简单的方法。
spo='one;two;three'
OIFS=$IFS
IFS=';'
spo_array=($spo)
IFS=$OIFS
echo ${spo_array[*]}
#27楼
如果您不介意立即处理它们,我喜欢这样做:
for i in $(echo $IN | tr ";" "\n")
do
# process
done
您可以使用这种循环来初始化数组,但是可能有一种更简单的方法来执行此操作。 希望这会有所帮助。
#28楼
echo "bla@some.com;john@home.com" | sed -e 's/;/\n/g'
bla@some.com
john@home.com
#29楼
您可以设置内部字段分隔符 (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"
#30楼
这种方法怎么样:
IN="bla@some.com;john@home.com"
set -- "$IN"
IFS=";"; declare -a Array=($*)
echo "${Array[@]}"
echo "${Array[0]}"
echo "${Array[1]}"