如何在Bash Shell中将一个字符串分割为多个变量?

260

我一直在寻找解决方案,并发现类似的问题,只不过它们试图使用空格来分割句子,而答案并不适用于我的情况。

目前有一个变量被设置为像这样的字符串:
ABCDE-123456
我想将其拆分为2个变量,同时消除-。 即:
var1=ABCDE
var2=123456

如何实现这一点?


这是对我有效的解决方案:
var1=$(echo $STR | cut -f1 -d-)
var2=$(echo $STR | cut -f2 -d-)

是否可能使用cut命令来分割没有分隔符的字符串(每个字符都被设置为一个变量)?

var1=$(echo $STR | cut -c1)
var2=$(echo $STR | cut -c2)
var3=$(echo $STR | cut -c3)
等等。


关于你的第二个问题,请参考下面我的回答中@mkb的评论 - 那绝对是正确的方法! - Rob I
1
查看我的修改后的答案,了解将单个字符读入数组的一种方法。 - Dennis Williamson
1
这里是更加简洁的形式: var1=$(cut -f1 -d- <<<$STR) - Nick Weedon
5个回答

313

要按照-分割字符串,您可以使用带有IFSread

$ IFS=- read -r var1 var2 <<< ABCDE-123456
$ echo "$var1"
ABCDE
$ echo "$var2"
123456

编辑:

以下是如何将每个单独字符读入数组元素的方法:

$ read -ra foo <<<"$(echo "ABCDE-123456" | sed 's/./& /g')"

转储数组:

$ declare -p foo
declare -a foo='([0]="A" [1]="B" [2]="C" [3]="D" [4]="E" [5]="-" [6]="1" [7]="2" [8]="3" [9]="4" [10]="5" [11]="6")'
如果字符串中有空格:
$ IFS=$'\v' read -ra foo <<<"$(echo "ABCDE 123456" | sed $'s/./&\v/g')"
$ declare -p foo
declare -a foo='([0]="A" [1]="B" [2]="C" [3]="D" [4]="E" [5]=" " [6]="1" [7]="2" [8]="3" [9]="4" [10]="5" [11]="6")'

5
好的,优雅的仅使用bash的方式,避免不必要的分叉。 - insecure
2
这个解决方案的好处是,如果分隔符不存在,var2 将为空。 - Martin Serrano
一种更通用、更正确的方法:IFS=- read -r -d '' var1 var2 < <(printf %s "ABCDE-123456")-r -d ''<(printf %s ...) 是重要的。 - Fravadona
1
@akwky:使用备用文件描述符while read -r line <&3; do ssh_or_something "$line"; done 3<file - Dennis Williamson
@JerryGreen:在 MacOS 上,为什么 Bash 3 不起作用?我没有看到任何区别。 - Dennis Williamson
显示剩余2条评论

273

如果您知道它只有两个字段,您可以像这样跳过额外的子进程,使用:

var1=${STR%-*}
var2=${STR#*-}
${STR%-*}会从字符串结尾开始删除最短的与模式-*匹配的子字符串。${STR#*-}也是类似,只不过它是从字符串开头开始匹配*-模式。它们分别有对应的%%##版本,用于查找最长的锚定模式匹配。如果有帮助记忆哪个做什么的提示,请告诉我!我总是需要尝试两者才能记住。

更多信息请参见bash文档


20
掌握您的POSIX shell功能,避免昂贵的fork和pipe以及bashisms,这是一个加分项。 - Jens
3
不确定“缺少bashisms”是否正确,因为这已经相当神秘了...如果您的分隔符是换行符而不是连字符,那么它会变得更加神秘。另一方面,它可以使用换行符,所以就是这样。 - Steven Lu
5
我终于找到了它的文档:Shell-Parameter-Expansion - Marek Podyma
29
助记符:"#"在标准键盘上位于"%"的左边,因此"#"可以移除前缀(在左边),"%"可以移除后缀(在右边)。 - DS.
4
另一个助记符,因为你的键盘可能不同(有些人只是“感觉”布局,而不是知道它):百分号符号通常出现在数字后面,例如90%,因此它是后缀。井号符号通常用于前导注释甚至只是哈希标签中的第一个字符,因此它是常见的前缀。这两个修饰符的目的是删除,一个仅删除前缀(#),另一个删除后缀(%)。 - Oliver W.
显示剩余4条评论

197

如果你的解决方案不必是通用的,比如只需要处理类似于你示例中的字符串,可以这么做:

var1=$(echo $STR | cut -f1 -d-)
var2=$(echo $STR | cut -f2 -d-)

我选择了cut,因为你可以仅仅扩展这些代码来适应更多的变量...


你能再看一遍我的帖子并查看是否有解决方案来回答后续问题吗?谢谢! - crunchybutternut
你也可以使用 cut 命令来截取字符!例如,cut -c1 - Matt K
1
尽管这个代码非常容易阅读和编写,但是它非常慢,因为它会强制你读取两次相同的数据($STR)... 如果你关注脚本性能,@anubhava的解决方案更好。 - FSp
1
除了作为丑陋的最后手段解决方案外,这个方法还有一个漏洞:除非你特别想让shell扩展字符串中的任何通配符作为副作用,否则你应该绝对使用双引号 echo "$STR"。参见 https://dev59.com/NWkw5IYBdhLWcg3wMHum - tripleee
1
当然,你说的双引号是对的,但我指出这个解决方案并不通用。然而,我认为你的评估有点不公平——对于某些人来说,这个解决方案可能更易读(因此更易扩展等),并且不完全依赖于晦涩的bash特性,这些特性在其他shell中无法转换。我猜这就是为什么我的解决方案虽然不太优雅,但仍然会定期获得投票的原因... - Rob I

54
听起来像是需要使用自定义 IFSset 命令完成的工作。
IFS=-
set $STR
var1=$1
var2=$2

你需要在一个带有本地IFS变量的函数中执行此操作,这样您就不会破坏其他需要IFS以预期方式运行的脚本部分。


不错 - 我知道 $IFS 但是没看过它怎么用。 - Rob I
我使用了 triplee 的示例,并且它完全按照广告中的方式工作!如果您需要在多个“抛出”变量的脚本中存储它们,只需更改最后两行:<pre> myvar1=echo $1 && myvar2=echo $2 </pre> - Sigg3.net
1
不要在反引号中使用无用的“echo”。 - tripleee
4
如果我们需要编写非Bash特定的内容,这是一个非常好的解决方案。为了处理IFS问题,可以在覆盖之前在开头添加“OLDIFS=$IFS”,然后在“set”行后立即添加“IFS=$OLDIFS”。 - Daniel Andersson
3
set -- $STR 之前添加 set -f 以禁用路径扩展,否则如果 $STR 包含模式,则会捕获路径文件名。 - Léa Gris
显示剩余4条评论

35

使用bash的正则表达式功能:

re="^([^-]+)-(.*)$"
[[ "ABCDE-123456" =~ $re ]] && var1="${BASH_REMATCH[1]}" && var2="${BASH_REMATCH[2]}"
echo $var1
echo $var2

输出

ABCDE
123456

2
喜欢预定义 re,以备将来使用! - Cometsong

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接