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


2039

我将此字符串存储在变量中:

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

现在我想用;定界符分割字符串,这样我就可以:

ADDR1="bla@some.com"
ADDR2="john@home.com"

我不一定需要 ADDR1ADDR2变量。如果它们是数组的元素,那就更好了。


经过以下答案的建议后,我得出了以下结论:

#!/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可以使用。


14
关于“ Edit2”:您可以简单地“取消设置IFS”,它将返回默认状态。除非您出于某些原因期望已将其设置为非默认值,否则无需显式保存和还原它。此外,如果要在函数内部执行此操作(如果不是,为什么不这样做?),则可以将IFS设置为局部变量,并且一旦退出该函数,它将返回其先前的值。
布鲁克斯·摩西

19
@BrooksMoses:(a)+1以local IFS=...尽可能使用;(b)-1为unset IFS,虽然我相信未设置的IFS的行为与IFS的默认值($'\ t \ n')相同,但这并不能完全将IFS重置为其默认值,但是似乎不可行盲目假设您的代码将不会在IFS设置为自定义值的情况下调用;(c)另一个想法是调用一个子shell:(IFS=$custom; ...)当该子shell退出时,IFS将返回其原始状态。
dubiousjim

我只想快速了解确定将可执行文件扔到哪里的路径,因此我选择使用run ruby -e "puts ENV.fetch('PATH').split(':')"。如果您想保持纯正bash的效果,则无济于事,但是使用具有内置拆分功能的任何脚本语言都更加容易。
nicooga,2016年

4
for x in $(IFS=';';echo $IN); do echo "> [$x]"; done
user2037659

2
为了将其保存为数组,我必须放置另一组括号并将其更改\n为一个空格。所以最后一行是mails=($(echo $IN | tr ";" " "))。因此,现在我可以mails使用数组表示法mails[index]或仅在循环中进行迭代来检查的元素
怪诞的'18

Answers:


1232

您可以设置内部字段分隔符(IFS)变量,然后将其解析为数组。如果在命令中发生这种情况,则IFS仅对单个命令的环境(to 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"

22
这可能是最好的方法。IFS的当前值将保留多长时间,是否可以通过设置本不应该使用的代码来使我的代码混乱,并且在完成后如何重置它?
克里斯·卢茨

7
现在,在应用此修复程序之后,仅在读取命令的持续时间内:)
Johannes Schaub-litb

14
您可以一次读取所有内容,而无需使用while循环:read -r -d''-a addr <<<“ $ in”#-d''在这里是关键,它告诉read不要在第一个换行符处停止(这是默认的-d),但要持续到EOF或NULL字节(仅出现在二进制数据中)。
2009年

55
@LucaBorrione 与没有分号或其他分隔符的命令设置IFS在同一行read,而不是在单独的命令中,将其作用域限定为该命令-因此它总是“恢复”;您无需手动执行任何操作。
查尔斯·达菲

5
@imagineerThis有一个涉及到这里的字符串和对IFS的局部更改的bug,需要$IN引起引用。该错误已在bash4.3中修复。
chepner 2014年

971

取自Bash shell脚本split array

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;

84
我只想补充一下:这是最简单的方法,您可以使用$ {arrIN [1]}(当然
从零开始

26
发现它:在$ {}中修改变量的技术称为“参数扩展”。
KomodoDave 2012年

22
不,我认为在存在空格的情况下也不行。它是将','转换为'',然后建立一个以空格分隔的数组。
伊桑(Ethan)2013年

12
非常简洁,但是有一些一般性警告:shell 对字符串应用了单词拆分扩展,这可能是不希望的。尝试一下。IN="bla@some.com;john@home.com;*;broken apart"。简而言之:如果您的令牌包含嵌入式空格和/或字符,则此方法将无效。例如*碰巧使令牌与当前文件夹中的文件名匹配。
mklement0 2013年

53
由于其他原因,这是一种不好的方法:例如,如果您的字符串包含;*;,则将*扩展为当前目录中的文件名列表。-1
Charles Duffy 2013年

249

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

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

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


您应该保留IFS答案。它教给我一些我不知道的东西,它肯定组成了一个数组,而这只是一个廉价的替代品。
克里斯·卢茨

我知道了。是的,我发现自己做了这些愚蠢的实验,每次尝试回答问题时,我都会学习新事物。我已经基于#bash IRC反馈编辑了内容,并且未删除:)
Johannes Schaub-litb

33
-1,您显然不知道分词,因为它在您的代码中引入了两个错误。一种是当您不引用$ IN时,另一种是您假装换行符是单词拆分中使用的唯一定界符。您正在遍历IN中的每个WORD,而不是每一行,并且肯定不是遍历每个由分号分隔的元素,尽管它看起来似乎具有其正常工作的副作用。
2009年

3
您可以更改它以回显“ $ IN” | tr';' '\ n'| 而读-r ADDY; #处理“ $ ADDY”;做完了,让他很幸运,我想:)请注意,这将派生,并且您不能从循环内更改外部变量(这就是为什么我使用<<<“ $ IN”语法)然后
Johannes Schaub-litb

8
总结评论中的辩论:通用警告:shell 对字符串应用单词拆分扩展,这可能是不希望的;尝试一下。IN="bla@some.com;john@home.com;*;broken apart"。简而言之:如果您的令牌包含嵌入式空格和/或字符,则此方法将无效。例如*碰巧使令牌与当前文件夹中的文件名匹配。
mklement0 2013年

202

兼容答案

有很多不同的方法可以做到这一点

但是,重要的是要首先注意到它bash具有许多其他功能无法使用的特殊功能(所谓的bashisms

特别是,本文中的解决方案以及线程中的其他解决方案中使用的array关联数组模式替换是一种bashisms,可能无法在许多人使用的其他shell下工作。

例如:在我的Debian GNU / Linux上,有一个标准叫壳; 我知道很多人喜欢使用另一个称为; 还有一个叫做 用他自己的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),

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>]

玩得开心!


15
###%,和%%换人有什么是IMO的解释更容易记住(对他们有多删除):#%删除最短的匹配字符串,##%%删除可能最长的。
2015年

1
IFS=\; read -a fields <<<"$var"对新行失败,并添加一个结尾的新行。另一种解决方案是删除尾随的空白字段。
艾萨克(Isaac)2016年

外壳定界符是最优雅的答案。
艾瑞克·陈

最后的替代方法可以与其他地方设置的字段分隔符列表一起使用吗?例如,我的意思是将其用作shell脚本,并将字段分隔符列表作为位置参数传递。
sancho.s ReinstateMonicaCellio '18

是的,处于一个循环中:for sep in "#" "ł" "@" ; do ... var="${var#*$sep}" ...
F. Hauri

183

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


6
使用的荣誉cut,这是完成工作的正确工具!比所有这些shell hack清除得多。
MiyaMiyagi '16

4
这种方法只有在您事先知道元素数量的情况下才有效;您需要围绕它编写更多的逻辑。它还为每个元素运行一个外部工具。
uli42

非常高兴我一直在寻找尝试避免csv中的空字符串。现在,我也可以指出确切的“列”值。与已经在循环中使用的IFS一起使用。我的情况比预期的要好。
Louis Loudog Trottier

对于拉动ID和PID也非常有用,例如
Milos Grujic

这个答案值得向下滚动半页:)
Gucu112

124

这为我工作:

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

1
尽管它只能与单个字符定界符一起使用,但这是OP所寻找的(用分号分隔的记录)。
GuyPaddock

通过回答大约在四年前@Ashok,而且,一年多前由@DougW,比你的答案,甚至更多的信息。请发布与其他人不同的解决方案。
MAChitgarha

90

这种方法怎么样:

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

资源


7
+1 ...但我不会将变量命名为“ Array” ...我猜是pet peev。好的解决方案。
伊兹密尔·拉米雷斯

14
+1 ...,但是不需要“ set”和声明-a。你可以和刚使用IFS";" && Array=($IN)
ATA

+1只是一个旁注:保留旧的IFS然后还原它是否值得推荐?(如stefanB在他的edit3中所示)在这里着陆的人(有时只是复制和粘贴解决方案)可能不会考虑这一点
Luca Borrione 2012年

6
-1:首先,@ ata是正确的,此命令中的大多数命令什么都不做。其次,它使用分词来形成数组,并且这样做时并没有做任何事情来抑制glob扩展(因此,如果在任何array元素中都有glob字符,这些元素将被匹配的文件名替换)。
Charles Duffy

1
建议使用$'...'IN=$'bla@some.com;john@home.com;bet <d@\ns* kl.com>'。然后echo "${Array[2]}"将用换行符打印一个字符串。set -- "$IN"在这种情况下也是必要的。是的,为防止全局扩展,解决方案应包括set -f
John_West

79

我认为AWK是解决您问题的最佳且有效的命令。默认情况下,几乎每个Linux发行版都包含AWK。

echo "bla@some.com;john@home.com" | awk -F';' '{print $1,$2}'

会给

bla@some.com john@home.com

当然,您可以通过重新定义awk打印字段来存储每个电子邮件地址。


3
甚至更简单:echo“ bla@some.com; john@home.com” | awk'BEGIN {RS =“;”} {print}'
Jaro 2014年

@Jaro当我用逗号分隔字符串并且需要将其重新格式化为行时,这对我来说效果很好。谢谢。
水彩画

它在这种情况下有效->“ echo” $ SPLIT_0“ | awk -F'inode =''{print $ 1}''”!尝试使用atrings(“ inode =”)代替字符(“;”)时遇到问题。$ 1,$ 2,$ 3,$ 4被设置为数组中的位置!如果有一种设置数组的方法,那就更好了!谢谢!
爱德华多·卢西奥

@EduardoLucio,就是我想的是,也许你可以先更换您的分隔符inode=;例如通过sed -i 's/inode\=/\;/g' your_file_to_process,然后定义-F';'应用时awk,希望能帮助你。

66
echo "bla@some.com;john@home.com" | sed -e 's/;/\n/g'
bla@some.com
john@home.com

4
-1 如果字符串包含空格怎么办?例如IN="this is first line; this is second line" arrIN=( $( echo "$IN" | sed -e 's/;/\n/g' ) ),在这种情况下将产生8个元素的数组(每个单词空间分隔一个元素),而不是2个(每行半冒号分隔一个元素)
Luca Borrione 2012年

3
@Luca sed脚本不能精确地创建两行。当您将其放入bash数组(默认情况下会在空白处分割)时,为您创建多个条目的原因
lothar 2012年

这就是重点:正如他在编辑中所看到的,OP需要将条目存储到数组中以对其进行循环。我认为您的(好)答案没有提到用于arrIN=( $( echo "$IN" | sed -e 's/;/\n/g' ) )实现该目标的建议,也没有建议将IFS更改IFS=$'\n'为将来在此使用并需要拆分包含空格的字符串的人。(然后将其还原)。:)
卡·博里昂

1
@Luca好点。但是,当我写下答案时,数组分配并不是最初的问题。
lothar 2012年

65

这也适用:

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。


1
您可以使用-s来避免提到的问题:superuser.com/questions/896800/… “ -f,--fields = LIST仅选择这些字段;还可以打印不包含定界符的任何行,除非-s选项为指定”
fersarr

34

Darron的答案有不同的看法,这就是我的做法:

IN="bla@some.com;john@home.com"
read ADDR1 ADDR2 <<<$(IFS=";"; echo $IN)

我认为是!运行上面的命令,然后“ echo $ ADDR1 ... $ ADDR2”,我得到“ bla@some.com ... john@home.com”输出
nickjb 2011年

1
这对我来说真的很好...我用它来遍历包含逗号分隔的DB,SERVER,PORT数据的字符串数组,以使用mysqldump。
尼克,

5
诊断:IFS=";"分配仅存在于$(...; echo $IN)子外壳中;这就是为什么一些读者(包括我)最初认为它不起作用的原因。我以为所有$ IN都会被ADDR1吞噬。但是nickjb是正确的;它确实有效。原因是该echo $IN命令使用$ IFS的当前值来解析其参数,但是无论$ IFS的设置如何,都使用空格分隔符将它们回显到stdout。因此,最终的效果就像一个人已经调用过read ADDR1 ADDR2 <<< "bla@some.com john@home.com"(请注意,输入是用空格分隔而不是;分隔)。
dubiousjim

1
这在空格和换行符上会失败,并且还会*echo $IN引号中使用不带引号的变量扩展来扩展通配符。
艾萨克(Isaac)

我真的很喜欢这个解决方案。对它为什么起作用的描述将非常有用,并使它成为更好的总体答案。
迈克尔·加斯基

32

在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")'

起作用的技巧是将(delimiter)-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的更新

从Bash 4.4开始,内置的mapfile(aka readarray)支持-d指定分隔符的选项。因此,另一种规范的方式是:

mapfile -d ';' -t array < <(printf '%s;' "$in")

5
我发现它是该列表上罕见的解决方案,可与\n,空格*同时正确使用。同样,没有循环;执行后,可以在外壳程序中访问数组变量(与最高答案相反)。请注意,in=$'...'不适用于双引号。我认为,它需要更多的支持。
John_West

28

如果您不使用数组,那么这种衬板怎么样:

IFS=';' read ADDR1 ADDR2 <<<$IN

考虑使用read -r ...以确保例如输入中的两个字符“ \ t”最终与变量中的两个相同字符(而不是单个制表符)成为同一字符。
dubiousjim

-1在这里不起作用(ubuntu 12.04)。添加echo "ADDR1 $ADDR1"\n echo "ADDR2 $ADDR2"到您的代码段中将输出ADDR1 bla@some.com john@home.com\nADDR2(\ n为换行符)
Luca Borrione 2012年

这可能是由于IFSbash4.3 中修复了一个涉及字符串的错误。报价$IN应该解决它。(从理论上讲,$IN扩展后就不会出现单词分裂或模糊不清的现象,这意味着引号应该是不必要的。尽管在4.3中,仍然存在至少一个错误-报告并计划固定,因此引用仍然是一个不错的选择。想法。)
chepner 2015年

如果$ in包含换行符,即使$ IN被引用,这也会中断。并添加尾随换行符。
艾萨克(Isaac)2016年

与此有关的问题以及许多其他解决方案还假定$ IN中确实有两个元素-或者您愿意将ADDR2中的第二个及后续项目粉碎在一起。我知道这符合要求,但这是定时炸弹。
轻松逗乐的史蒂文(Steven)


20

这是干净的三层纸:

in="foo@bar;bizz@buzz;fizz@buzz;buzz@woof"
IFS=';' list=($in)
for item in "${list[@]}"; do echo $item; done

其中IFS基于分隔符分隔单词并()用于创建数组。然后[@]用于将每个项目作为单独的单词返回。

如果之后有任何代码,则还需要还原$IFS,例如unset IFS


5
使用不带$in引号的通配符可以被扩展。
艾萨克(Isaac)

10

以下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需要-aBash和-Azsh中的标志。

如果需要,可以将该函数放入脚本中,如下所示:

#!/usr/bin/env bash

split() {
    # ...
}

split "$@"

似乎不能使用超过1个字符的分隔符:split = $(split“ $ content”“ file://”)
madprops

真实-从help read-d delim continue until the first character of DELIM is read, rather than newline
哈雷·纳斯特

8

您可以在许多情况下使用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"

7

有一种简单而聪明的方法,如下所示:

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 {}

4

这是最简单的方法。

spo='one;two;three'
OIFS=$IFS
IFS=';'
spo_array=($spo)
IFS=$OIFS
echo ${spo_array[*]}

4

这里有一些很酷的答案(特别是错误),但是对于类似于其他语言的东西来说,这就是我最初想表达的意思,我就此解决了:

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在这种情况下,请使用解决方案。


IN包含两个以上的电子邮件地址时不起作用。请在palindrom的答案中
olibre

更好地使用${IN//;/ }(双斜杠)使其也可以使用两个以上的值。请注意,任何通配符(*?[)都会被扩展。并且尾随的空白字段将被丢弃。
艾萨克(Isaac)2016年

3
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


没有在read此处的特定上下文中设置IFS ,因此,它可能会使其余代码(如果有的话)不满意。
codeforester

2

如果没有空间,为什么不这样做呢?

IN="bla@some.com;john@home.com"
arr=(`echo $IN | tr ';' ' '`)

echo ${arr[0]}
echo ${arr[1]}

2

使用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

更好地使用set -- $IN以避免从破折号开始的“ $ IN”问题。不过,未引号的扩展$IN会扩展通配符(*?[)。
艾萨克(Isaac)

2

两个不需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-)}


2

除了已经提供的奇妙答案外,如果仅打印出数据,您可以考虑使用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]

2

在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

其中//表示全球替换。


1
如果$ PATH的任何部分包含空格(或换行符),则失败。还扩展通配符(星号*,问号?和大括号[…])。
艾萨克(Isaac)

2
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”指出这一点。


!"#$%&/()[]{}*? are no problem好吧...不完全是:[]*?全球字符。那么创建该目录和文件呢:`mkdir'!“#$%&';触摸'!”#$%&/()[] {}让您哈哈哈哈-没问题”并运行命令?简单也许很漂亮,但是当它被打破时,它就被打破了。
gniourf_gniourf

@gniourf_gniourf字符串存储在变量中。请参阅原始问题。
ajaaskel

1
@ajaaskel您不完全理解我的评论。进入临时目录并发出以下命令:mkdir '!"#$%&'; touch '!"#$%&/()[]{} got you hahahaha - are no problem'。我必须承认,他们只会创建一个目录和文件,它们的名称看起来很奇怪。然后用精确的运行命令IN你给了:IN='bla@some.com;john@home.com;Charlie Brown <cbrown@acme.com;!"#$%&/()[]{}*? are no problem;simple is beautiful :-)'。您会看到您不会获得期望的输出。因为您使用的是经过路径名扩展的方法来拆分字符串。
gniourf_gniourf

这是证明的人物*?[...]甚至,如果extglob设置,!(...)@(...)?(...)+(...) 都是用这种方法的问题!
gniourf_gniourf 2015年

1
@gniourf_gniourf感谢您对通气的详细评论。我调整了代码以使其通通。但是,我的意思只是表明,相当简单的分配可以完成拆分工作。
ajaaskel 2015年

1

大家好!

这是我的答案!

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

为什么这种方法对我来说是“最好的”?

有两个原因:

  1. 无需转义分隔符;
  2. 您将不会有空格的问题。该值将在数组中正确分隔!

[]的


仅供参考,/etc/os-release并且/etc/lsb-release是为了进行采购,而不是解析。因此,您的方法确实是错误的。此外,您还没有完全回答有关在定界符上分割字符串
gniourf_gniourf

0

单线分割用';'分隔的字符串 放入数组是:

IN="bla@some.com;john@home.com"
ADDRS=( $(IFS=";" echo "$IN") )
echo ${ADDRS[0]}
echo ${ADDRS[1]}

这仅将IFS设置在子外壳中,因此您不必担心保存和恢复其值。


-1,这在这里不起作用(ubuntu 12.04)。它仅打印第一个回显,其中包含所有$ IN值,而第二个为空。如果将echo“ 0:” $$ ADDRS [0]} \ n输入echo“ 1:” $ {ADDRS [1]},则输出为0: bla@some.com;john@home.com\n 1:(\ n为换行符)
Luca Borrione 2012年

1
请参考nickjb的答案,以获取该想法的替代方案stackoverflow.com/a/6583589/1032370
Luca Borrione 2012年

1
-1,1.未在该子shell中设置IFS(它被传递到内置的“ echo”环境中,因此无论如何都不会发生)。2.用$IN引号引起来,因此不受IFS拆分。3.进程替换由空格分隔,但这可能会破坏原始数据。
2015年

0

也许不是最优雅的解决方案,但可以和*and空格一起使用:

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从循环1number-of-delimiters,由计数${#delims}。最后一步是使用来安全地获取该$i部分cut

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.