题 如何在Bash中的分隔符上拆分字符串?


我把这个字符串存储在一个变量中:

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 回到默认状态?

回覆: 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 有用。


1514
2018-05-28 02:03


起源


关于你的“Edit2”:你可以简单地“取消设置IFS”,它将返回默认状态。除非您有理由预期它已被设置为非默认值,否则无需显式保存和恢复它。此外,如果你在一个函数内执行此操作(如果不是,为什么不这样做?),可以将IFS设置为局部变量,并在退出函数后返回其先前的值。 - Brooks Moses
@BrooksMoses:(a)使用+1 local IFS=... 在可能的情况; (b)-1表示 unset IFS,这并没有完全将IFS重置为其默认值,但我相信未设置的IFS与IFS的默认值($'\ t \ n')的行为相同,但是盲目地假设您的代码似乎是不好的做法永远不会在IFS设置为自定义值的情况下调用; (c)另一个想法是调用子shell: (IFS=$custom; ...) 子shell退出时,IFS将返回原来的状态。 - dubiousjim
我只是想快速浏览一下决定在哪里抛出可执行文件的路径,所以我求助于运行 ruby -e "puts ENV.fetch('PATH').split(':')"。如果你想保持纯粹的bash会不由自主地使用 任何脚本语言 内置拆分更容易。 - nicooga
这是一种偷渡式评论,但由于OP使用电子邮件地址作为示例,有人打扰以完全符合RFC 5322的方式回答它,即任何带引号的字符串都可以出现在@之前,这意味着你'需要正则表达式或其他类型的解析器,而不是天真地使用IFS或其他简单的分离器函数。 - Jeff
for x in $(IFS=';';echo $IN); do echo "> [$x]"; done - user2037659


答案:


你可以设置 内部字段分隔符 (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"

927
2018-05-28 02:23



这可能是最好的方法。 IFS会持续多久它的当前值,它可以通过设置它不应该被设置来搞乱我的代码,以及如何在我完成它时重置它? - Chris Lutz
现在应用修复后,仅在读取命令的持续时间内:) - Johannes Schaub - litb
你可以在不使用while循环的情况下一次读取所有内容:read -r -d''-a addr <<<“$ in”#-d''在这里是关键,它告诉read不要停在第一个换行符处(这是默认的-d)但要继续直到EOF或NULL字节(仅出现在二进制数据中)。 - lhunath
@LucaBorrione设定 IFS 与...在同一条线上 read 没有分号或其他分隔符,而不是在单独的命令中,将其范围限定为该命令 - 因此它总是“恢复”;你不需要手动做任何事情。 - Charles Duffy
@imagineerThis有一个涉及herestrings和IFS本地更改的错误需要 $IN 被引用。该错误已修复 bash 4.3。 - chepner


取自 Bash shell脚本拆分数组

IN="bla@some.com;john@home.com"
arrIN=(${IN//;/ })

说明:

这种结构取代了所有出现的情况 ';' (最初的 // 在字符串中表示全局替换 IN 同 ' ' (单个空格),然后将空格分隔的字符串解释为数组(这就是周围的括号所做的)。

花括号内部使用的语法来替换每个 ';' 与...的性格 ' ' 字符被称为 参数扩展

有一些常见的问题:

  1. 如果原始字符串有空格,则需要使用 IFS
    • IFS=':'; arrIN=($IN); unset IFS;
  2. 如果原始字符串有空格  分隔符是一个新行,你可以设置 IFS 有:
    • IFS=$'\n'; arrIN=($IN); unset IFS;

744
2018-03-10 09:00



我只想补充一点:这是最简单的,你可以使用$ {arrIN [1]}访问数组元素(当然从0开始) - Oz123
找到它:在$ {}内修改变量的技术称为“参数扩展”。 - KomodoDave
当原始字符串包含空格时它是否有效? - qbolec
不,我不认为当存在空间时它会起作用......它将','转换为''然后构建一个空格分隔的数组。 - Ethan
出于其他原因,这是一种糟糕的方法:例如,如果您的字符串包含 ;*;那么 * 将扩展为当前目录中的文件名列表。 -1 - Charles Duffy


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

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

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


207
2018-05-28 02:09



您应该保留IFS答案。它告诉了我一些我不知道的东西,它肯定是一个阵列,而这只是一个廉价的替代品。 - Chris Lutz
我懂了。是的,我发现做了这些愚蠢的实验,每次我试图回答的时候,我都会学习新东西。我根据#bash IRC反馈和未删除的内容编辑了东西:) - Johannes Schaub - litb
-1,你显然不知道wordplitting,因为它在你的代码中引入了两个bug。一个是你不引用$ IN而另一个是你假装换行是wordplitting中唯一使用的分隔符。你正在迭代IN中的每个WORD,而不是每一行,并且确定并不是每个由分号分隔的元素,尽管它看起来可能具有看起来像它有效的副作用。 - lhunath
您可以将其更改为回显“$ IN”| tr';' '\ n'|读取-r ADDY;做#process“$ ADDY”;为了让他幸运,我想:)请注意,这将分叉,你不能从循环内改变外部变量(这就是为什么我使用<<<“$ IN”语法)然后 - Johannes Schaub - litb
总结评论中的争论: 一般用途的注意事项:shell适用 分词 和 扩展 字符串,可能是不希望的;试试吧。 IN="bla@some.com;john@home.com;*;broken apart"。简而言之:如果您的令牌包含嵌入的空格和/或字符,则此方法将会中断。如 * 恰好在当前文件夹中创建令牌匹配文件名。 - mklement0


兼容的答案

对于这个问题,已经有很多不同的方法来做到这一点 。 但是bash有很多 特别 所谓的功能 bashism 效果很好,但这在任何其他方面都无效

尤其是, 阵列关联数组,和 模式替代 很纯洁 bash化 并且可能不适用于其他 炮弹

在我的 Debian GNU / Linux,有一个 标准 贝壳叫 ,但我知道很多人喜欢使用

最后,在非常小的情况下,有一个特殊的工具叫做  用他自己的shell解释器()。

请求的字符串

SO问题中的字符串示例是:

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

因为这可能有用 空格 并作为 空格 可以修改例程的结果,我更喜欢使用这个示例字符串:

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

基于分隔符的拆分字符串  (版本> = 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


123
2018-04-13 14:20



该 #, ##, %,和 %% 替换有什么是IMO更容易记住的解释(他们删除了多少): # 和 % 删除最短的匹配字符串,然后 ## 和 %% 删除最长的。 - Score_Under
该 IFS=\; read -a fields <<<"$var" 在换行符上失败并添加尾随换行符。另一个解决方案删除尾随空字段。 - sorontar
shell分隔符是最优雅的答案,句号。 - Eric Chen


这种方法怎么样:

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

资源


80
2018-05-28 10:31



+1 ...但我不会将变量命名为“数组”...我想是宠物peev。好的解决方案 - Yzmir Ramirez
+1 ......但是“set”和声明-a是不必要的。你也可以使用 IFS";" && Array=($IN) - ata
+1只是附注:不应该建议保留旧的IFS然后恢复它吗? (如stefanB在他的编辑3中所示)登陆这里的人(有时只是复制和粘贴解决方案)可能不会想到这个 - Luca Borrione
-1:首先,@ is是正确的,其中的大多数命令都不起作用。其次,它使用分词来形成数组,并且在执行此操作时不执行任何操作来禁止glob扩展(因此,如果在任何数组元素中都有glob字符,则这些元素将替换为匹配的文件名)。 - Charles Duffy
建议使用 $'...': IN=$'bla@some.com;john@home.com;bet <d@\ns* kl.com>'。然后 echo "${Array[2]}" 将使用换行符打印一个字符串。 set -- "$IN" 在这种情况下也是必要的。是的,为了防止全局扩展,解决方案应该包括 set -f。 - John_West


我已经看到了几个引用它的答案 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 此文件并选择特定字段以进行进一步处理。


75
2018-04-27 18:20



荣誉使用 cut,这是工作的正确工具!比任何贝壳黑客都清除得多。 - MisterMiyagi
只有事先知道元素的数量,这种方法才有效;你需要围绕它编写一些更多的逻辑。它还为每个元素运行外部工具。 - uli42
我正兴奋地试图避免在csv中出现空字符串。现在我也可以指出确切的“列”值。使用已在循环中使用的IFS。对我的情况好于预期。 - Louis Loudog Trottier


这对我有用:

string="1;2"
echo $string | cut -d';' -f1 # output is 1
echo $string | cut -d';' -f2 # output is 2

67
2017-08-11 20:45



这很好吃:) - Pardeep Sharma
谢谢......帮了很多忙 - space earth
cut只能使用一个char作为分隔符。 - mojjj