split()
在 JavaScript 中使用将字符串分解为数组非常容易。
shell脚本呢?
假设我想这样做:
$ script.sh var1_var2_var3
当用户将这样的字符串提供var1_var2_var3
给 script.sh 时,在脚本内部它会将字符串转换为数组,例如
array=( var1 var2 var3 )
for name in ${array[@]}; do
# some code
done
答案1
类似 Bourne/POSIX 的 shell 有一个 split+glob 运算符,每次在列表上下文中不加引号的参数扩展 ( $var
, $-
...)、命令替换 ( $(...)
) 或算术扩展 ( ) 时都会调用它。$((...))
for name in ${array[@]}
实际上,当您调用它而不是 时,您错误地调用了它for name in "${array[@]}"
。 (实际上,你应该注意的是错误地调用该运算符是许多错误和安全漏洞的根源)。
该运算符配置有$IFS
特殊参数(以告诉要分割的字符(但要注意空格、制表符和换行符在那里受到特殊处理))以及-f
禁用 ( set -f
) 或启用 ( set +f
) 该glob
部分的选项。
另请注意,虽然S
in$IFS
最初(在 Bourne shell 中来自$IFS
)用于S分隔符,但在 POSIX shell 中, in 中的字符$IFS
应该被视为分隔符或者终结者(参见下面的示例)。
所以要拆分_
:
string='var1_var2_var3'
IFS=_ # delimit on _
set -f # disable the glob part
array=($string) # invoke the split+glob operator
for i in "${array[@]}"; do # loop over the array elements.
要查看之间的区别分隔器和分隔符, 试一下:
string='var1_var2_'
这会将其分为var1
和var2
仅(没有额外的空元素)。
因此,为了使其与 JavaScript 类似split()
,您需要一个额外的步骤:
string='var1_var2_var3'
IFS=_ # delimit on _
set -f # disable the glob part
temp=${string}_ # add an extra delimiter
array=($temp) # invoke the split+glob operator
(请注意,它将把一个空$string
分成1(不是0) 元素,如 JavaScript 的split()
)。
要查看选项卡、空格和换行符的特殊处理,请比较:
IFS=' '; string=' var1 var2 '
(你得到的地方var1
和var2
)与
IFS='_'; string='_var1__var2__'
在那里你得到:''
, var1
, ''
, var2
, ''
.
请注意,除非在或仿真zsh
中,否则 shell 不会像这样隐式调用 split+glob 运算符。在那里,您必须显式调用它。对于分割部分,对于全局部分(对于两者),它还有一个分割运算符,您可以在其中指定分隔符:sh
ksh
$=string
$~string
$=~string
array=(${(s:_:)string})
或保留空元素:
array=("${(@s:_:)string}")
请注意,有s
用于分裂, 不是界定(还有$IFS
,已知的 POSIX 不符合项zsh
)。它与 JavaScript 的不同之处split()
在于,空字符串被分成 0 个(而不是 1 个)元素。
与 -splitting 的一个显着区别$IFS
是在字符串${(s:abc:)string}
上拆分abc
,而使用IFS=abc
, 则会在a
,b
或上拆分c
。
对于zsh
和ksh93
,空格、制表符或换行符所受到的特殊处理可以通过将它们加倍来删除$IFS
。
作为一个历史记录,Bourne shell(祖先或现代 POSIX shell)总是剥离空元素。它还存在许多与 $@ 与非默认值 的拆分和扩展相关的错误$IFS
。例如,IFS=_; set -f; set -- $@
不等于IFS=_; set -f; set -- $1 $2 $3...
.
根据正则表达式进行拆分
现在,对于可以根据正则表达式进行拆分的更接近 JavaScript 的东西split()
,您需要依赖外部实用程序。
在 POSIX 工具箱中,awk
有一个split
可以拆分的操作符扩展正则表达式(这些或多或少是 JavaScript 支持的类 Perl 正则表达式的子集)。
split() {
awk -v q="'" '
function quote(s) {
gsub(q, q "\\" q q, s)
return q s q
}
BEGIN {
n = split(ARGV[1], a, ARGV[2])
for (i = 1; i <= n; i++) printf " %s", quote(a[i])
exit
}' "$@"
}
string=a__b_+c
eval "array=($(split "$string" '[_+]+'))"
shellzsh
内置了对 Perl 兼容正则表达式的支持(在其zsh/pcre
模块中),但使用它来分割字符串虽然可能相对麻烦。
答案2
是的,使用IFS
并将其设置为_
。然后用于read -a
存储到数组中(-r
关闭反斜杠扩展)。请注意,这是特定于 bash 的; ksh 和 zsh 具有相似的功能,但语法略有不同,并且普通 sh 根本没有数组变量。
$ r="var1_var2_var3"
$ IFS='_' read -r -a array <<< "$r"
$ for name in "${array[@]}"; do echo "+ $name"; done
+ var1
+ var2
+ var3
从man bash
:
读
-A一个名字
这些单词被分配给数组变量 aname 的顺序索引,从 0 开始。在分配任何新值之前,aname 会被取消设置。其他名称参数将被忽略。
IFS
内部字段分隔符用于扩展后的字拆分以及使用 read 内置命令将行拆分为字。默认值为``''。
请注意,read
停在第一个换行符处。传递-d ''
toread
以避免这种情况,但在这种情况下,由于运算符的原因,末尾会出现额外的换行符<<<
。您可以手动删除它:
IFS='_' read -r -d '' -a array <<< "$r"
array[$((${#array[@]}-1))]=${array[$((${#array[@]}-1))]%?}