关于shell:如何在bash中分割分隔符上的字符串?

How do I split a string on a delimiter in Bash?

我有这个字符串变量存储在a

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

现在我想分裂的字符串是由一个分隔符:;

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

我不不需要的ADDR1ADDR2变量。如果他们是元素的数组这是甚至更好。

建议从下面的答案后,我来了,下面的是什么:是我。

1
2
3
4
5
6
7
8
9
10
11
#!/usr/bin/env bash

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

mails=$(echo $IN | tr";""
"
)

for addr in $mails
do
    echo"> [$addr]"
done

输出:

1
2
> [bla@some.com]
> [john@home.com]

有一个解决方案,包括设置内部_场_ ;分离器(IFS)。我不知道要发生什么事了吗,你怎么IFS重置回默认?

解决方案:一个IFS",这和它的作品,我把旧IFS然后恢复它:

1
2
3
4
5
6
7
8
9
10
11
IN="bla@some.com;john@home.com"

OIFS=$IFS
IFS=';'
mails2=$IN
for x in $mails2
do
    echo"> [$x]"
done

IFS=$OIFS

顺便说一句,当我尝试

1
mails2=($IN)

我只把它当打印字符串中的第一环,它在$IN没有括号。

  • 关于您的"edit2":您可以简单地"取消设置ifs",它将返回到默认状态。没有必要显式地保存和恢复它,除非您有理由希望它已经被设置为非默认值。此外,如果您在函数内部执行此操作(如果您没有,为什么不执行此操作?),可以将ifs设置为局部变量,一旦退出函数,它将返回到以前的值。
  • @brooksmoses:(a)+1用于尽可能使用local IFS=...;(b)-1用于unset IFS,这并不完全将ifs重置为其默认值,尽管我认为未设置的ifs的行为与ifs的默认值相同($' '),但是盲目假设ifs设置为自定义值时永远不会调用代码似乎是一种糟糕的做法;(c)另一个想法是调用一个子shell:当子shell退出时,ifs将返回原来的状态。
  • 我只想快速查看路径以决定将可执行文件扔到哪里,所以我使用运行ruby -e"puts ENV.fetch('PATH').split(':')"。如果你想保持纯粹的bash,那就没什么用了,但是使用任何具有内置拆分的脚本语言都比较容易。
  • 这是一种驱动注释,但是由于OP使用电子邮件地址作为示例,所以有人费心以完全符合RFC 5322的方式来回答它,也就是说,任何带引号的字符串都可以出现在@之前,这意味着您将需要正则表达式或某种其他类型的分析器,而不是简单地使用ifs或其他简单的语法。拆分器功能。
  • for x in $(IFS=';';echo $IN); do echo"> [$x]"; done
  • 为了将它保存为一个数组,我必须放置另一组括号,并将
    更改为一个空格。所以最后一行是mails=($(echo $IN | tr";"""))。所以现在我可以使用数组符号mails[index]或循环迭代来检查mails的元素。
  • 就其价值而言,tr解决方案在zsh中的工作方式不同。


您可以设置内部字段分隔符(IFS)变量,然后让它解析为一个数组。当在命令中发生这种情况时,对IFS的分配只发生在该单个命令的环境(对read的环境)中。然后,它根据IFS变量值将输入解析为一个数组,然后我们可以迭代该数组。

1
2
3
4
IFS=';' read -ra ADDR <<<"$IN"
for i in"${ADDR[@]}"; do
    # process"$i"
done

它将解析由;分隔的一行项目,并将其推送到一个数组中。用于处理整个$IN的资料,每次由;分隔的一行输入:

1
2
3
4
5
 while IFS=';' read -ra ADDR; do
      for i in"${ADDR[@]}"; do
          # process"$i"
      done
 done <<<"$IN"

  • 这可能是最好的方法。如果它的当前值持续多长时间,当它不应该被设置时,它会弄乱我的代码吗?当我完成它时,我如何重置它?
  • 现在,在应用修复之后,仅在read命令的持续时间内:)
  • 我知道有一种使用数组的方法,只是不记得它是什么。我喜欢设置ifs,但不确定从$in的重定向,并通过read来填充数组。恢复IFS不简单吗?不管怎样+1,如果有建议,谢谢。
  • 我不喜欢这个saved="$ifs";ifs=";";addr="$in";ifs="$saved"混乱。:)
  • 您可以一次读取所有内容,而不使用while循环:read-r-d''-a a d d r<<<"$in"-d''是这里的键,它告诉read不要在第一个换行符处停止(默认为-d),而是继续到eof或空字节(只出现在二进制数据中)。
  • lhunath,啊,好主意:)然而,当我说"-d"""时,它总是在数组中添加一个换行符作为最后一个元素。我不知道为什么:(
  • 在我看来,用一个自定义的单词分隔符以安全的方式拆分bash中的一行这一问题的自然解决方案。帮我很多忙。
  • 为什么不照我建议的那样做?我错过什么了吗?
  • +1只是一个旁注:难道不应该建议保留旧的国际单项体育联合会,然后恢复它吗?(如Stefanb在他的编辑3中所示)登陆这里的人(有时只是复制和粘贴一个解决方案)可能不会想到这一点。
  • IFS@ lucaborrione setting the same as the read在线与不在线semicolon or other as to a separator分开,opposed范围命令,命令它,我知道它总是"restored";* need to do anything manually。
  • 我的noticed parentheses are needed在美元。put into the Whole字符串得到其他地址[ 0 ]。为什么"这房子?
  • imagineerthis there is a bug involving @ herestrings changes to local and quoted $INto be that……直觉。臭虫固定在bashis the 4.3。
  • newlinedoes not parse(
    )正确,当IN曼弗雷迪或法拉利吗declared IN=$'...'when IN="..."类标准。看到它,尝试在forecho $i环,或declare -p ADDR。see that溶液在工作区。
  • 过程不newlines included。在trailing newline也报告。
  • 这一额外的produces if the empty string to分裂阵列元素有超过一个字符模式。
  • 在里德- NOT SUPPORTED(zsh):
  • litb @ johannesschaub -奇异的解决方案。一个out there is里找到自己还是多学习世界学院:壳牌-脚本)


取自bash shell脚本拆分数组:

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

说明:

这个结构用' '替换字符串IN中所有出现的';'(初始//表示全局替换),然后将空格分隔的字符串解释为一个数组(这就是周围括号所做的)。

大括号内使用的语法将每个';'字符替换为' '字符,称为参数扩展。

有一些常见的问题:

  • 如果原始字符串有空格,则需要使用ifs:
    • IFS=':'; arrIN=($IN); unset IFS;
  • 如果原始字符串有空格并且分隔符是新行,则可以使用以下命令设置ifs:
    • IFS=$'
      '; arrIN=($IN); unset IFS;
    • 只要to add this is the of:在扩元素,你可以访问阵列与arrin美元{ } [ 1 ](Starting from zeros学院课程)
    • 发现:在变频调整技术学院within the is known as美元是{}参数展开。
    • 如果你想在这样的特殊字符分割由波浪号(~)make sure as to Escape(EN =:arrin美元/ / {在} ~)
    • does when the original work contains EN字符串中?
    • 不,我不认为这是工作时也有发展空间。它的转换是茶,然后是建筑在空间分离的阵列。
    • 非常简洁,but there are for the caveats通用用途:分裂和扩张壳applies to the Word字符串,which may be with undesired;只是尝试它。IN="bla@some.com;john@home.com;*;broken apart"。这将打破在短的方法:如果你tokens contain,嵌入式和/或chars空间。*that such as to make a token匹配文件名发生在the current folder。
    • 这是一种糟糕的方法,因为其他原因:例如,如果字符串包含;*;,那么*将被扩展到当前目录中的文件名列表。- 1
    • 实际上,您可以使用IFS而不是参数扩展/替换来解决空间问题:IFS=':' arrIN=($IN),我认为这也更易于理解。
    • @设置IFS的Kylestrand,然后设置arrIN,与在单独的行上执行或由;分隔时相同。也就是说,只有当赋值出现在非赋值命令之前,赋值才是临时的。因此,在IFS=':' arrIN=($IN)之后,echo"$IFS":并在:上对单词进行拆分,用于后续的命令,这通常是不需要的。(这很容易忽略,因为当:不在$IFS中时,echo $var足以检查$var是否为:,因此,除了脚本的最后部分,IFS=':' arrIN=($IN) IFS=$' \t
      '
      IFS=':' arrIN=($IN); unset IFS可能更可取。
    • @以利雅甲干啊。这种不一致有什么好处吗?
    • @Kylestrand是的,在这种情况下,虽然变量分配的作用域是命令的,但是一个变量分配的作用域是另一个变量分配的,这并不是真正意义上的。shell在赋值(或运行命令)之前执行变量/参数扩展。例如,x=foo echo $x不输出foo,因为$xfoo分配给xecho运行之前被扩展。同样地,如果x=foo y=$x只在y=$x运行时才将foo分配给x,那么y将被分配给原来的$x(而不是foo,因为$x将在任何变量分配发生之前扩展。
    • @查理斯达菲这可以用set -f来避免:set -f; IN="bla@some.com;*;john@home.com"; arrIN=(${IN//;/ }); echo ${arrIN[1]}
    • @约翰·韦斯特,是的,这种方法可以通过修改全局状态来禁用全局绑定(并以IFS的形式对进一步的全局状态进行密切控制),但是……那么,当没有任何风险的情况下,你为什么要这样做?
    • @Ethan感谢您指出了空间存在时的问题,我很惊讶这个问题不在答案中。我自由地编辑了答案来提到这个gotcha,并为它提供了一个解决方案(还有一个gotcha)。(@eliahkagan感谢您提供与原始答案一致的良好解决方案。)
    • 不知道为什么IFS=';' declare -a arr=($IN)在这里没有得到更多的信任。不需要设置任何中间变量,IFS更改只适用于一个declare命令,我们扩展了IFS,而不必将其更改为其他变量。
    • IFS=':';不应该是IFS=';';来匹配输入字符串吗?在后面的例子中
    • 为什么在bash脚本中放入for循环时语法arrIN=(${IN//;/ })会中断?


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

    1
    2
    3
    4
    5
    for i in $(echo $IN | tr";""
    "
    )
    do
      # process
    done

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

    • 你应该保留国际单项体育联合会的答复。它教会了我一些我不知道的东西,它确实是一个数组,而这只是一个廉价的替代品。
    • 我懂了。是的,我发现做这些愚蠢的实验,每次我试图回答问题的时候,我都会学到新的东西。我已经根据bash irc反馈和未删除的内容进行了编辑:)
    • -1,显然您不知道分词,因为它在您的代码中引入了两个错误。一个是不引用$的时候,另一个是假装换行符是分词时唯一使用的分隔符。您正在迭代中的每一个单词,而不是每一行,当然也不是每一个由分号分隔的元素,尽管它可能看起来像是有效的。
    • 你可以在读-r addy;do process"$addy"的时候将其改为echo"$i n""n""n"以使他幸运,我想:)注意,这将分叉,并且你不能从循环内更改外部变量(这就是为什么我使用<<"$i n"语法的原因),然后
    • 为了总结评论中的争论:一般使用的注意事项:shell对字符串应用分词和扩展,这可能是不需要的;只需尝试一下。IN="bla@some.com;john@home.com;*;broken apart"。简而言之:如果您的令牌包含嵌入的空格和/或字符,这种方法将会中断。例如,使令牌与当前文件夹中的文件名匹配的*
    • 这是非常有帮助的答案。如IN=abc;def;123。我们还可以如何打印索引编号?echo $count $i ?


    兼容答案

    对于这个问题,在bash中已经有了很多不同的方法来实现这一点。但是bash有许多特殊的特性,即所谓的bashism,可以很好地工作,但是在任何其他shell中都不能工作。

    特别是,数组、关联数组和模式替换是纯粹的bashims,在其他shell下可能不起作用。

    在我的debian gnu/linux上,有一个叫做dash的标准shell,但我认识许多喜欢使用ksh的人。

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

    请求的字符串

    有问题的字符串示例是:

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

    因为这对空白很有用,而且空白可以修改例程的结果,所以我更喜欢使用这个示例字符串:

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

    基于bash中的分隔符拆分字符串(版本>=4.2)

    在纯bash下,我们可以使用数组和ifs:

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

    1
    2
    3
    4
    5
    oIFS="$IFS"
    IFS=";"
    declare -a fields=($var)
    IFS="$oIFS"
    unset oIFS

    1
    IFS=\; read -a fields <<<"$IN"

    在最近的bash中使用此语法不会更改当前会话的$IFS,但只会更改当前命令:

    1
    2
    3
    set | grep ^IFS=
    IFS=$' \t
    '

    现在,字符串var被拆分并存储到一个数组(名为fields)中:

    1
    2
    3
    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请求可变内容:

    1
    2
    3
    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是最快速的分割方法,因为没有分叉,也没有调用外部资源。

    从这里,您可以使用已经知道的语法来处理每个字段:

    1
    2
    3
    4
    5
    6
    for x in"${fields[@]}";do
        echo"> [$x]"
        done
    > [bla@some.com]
    > [john@home.com]
    > [Full Name <fulnam@other.org>]

    或者在处理后删除每个字段(我喜欢这种移位方法):

    1
    2
    3
    4
    5
    6
    7
    while ["$fields" ] ;do
        echo"> [$fields]"
        fields=("${fields[@]:1}")
        done
    > [bla@some.com]
    > [john@home.com]
    > [Full Name <fulnam@other.org>]

    甚至对于简单的打印输出(较短的语法):

    1
    2
    3
    4
    5
    printf"> [%s]
    "
    "${fields[@]}"
    > [bla@some.com]
    > [john@home.com]
    > [Full Name <fulnam@other.org>]

    更新:最近的bash>=4.4

    你可以玩mapfile

    1
    mapfile -td \; fields < <(printf"%s\0""$IN")

    此语法保留特殊字符、换行符和空字段!

    如果您不关心空字段,可以:

    1
    2
    3
    4
    mapfile -td \; fields <<<"$IN"
    fields=("${fields[@]%$'
    '}"
    )   # drop '
    ' added by '<<<'

    但您可以通过函数使用字段:

    1
    2
    3
    4
    5
    6
    7
    8
    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.
    "

    }

    mapfile < <(printf"%s\0""$IN") -td \; -c 1 -C myPubliMail

    (注:格式字符串末尾的\0是无用的,而您不关心字符串末尾的空字段)

    1
    mapfile < <(echo -n"$IN") -td \; -c 1 -C myPubliMail

    将呈现如下内容:

    1
    2
    3
    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语法添加的换行符:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    myPubliMail() {
        local seq=$1 dest="${2%$'
    '}"

        printf"Seq: %6d: Sending mail to '%s'..." $seq"$dest"
        # mail -s"This is not a spam...""$dest" </path/to/body
        printf"\e[3D, done.
    "

    }

    mapfile <<<"$IN" -td \; -c 1 -C myPubliMail

    将呈现相同的输出:

    1
    2
    3
    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下写一些有用的东西,你就不能使用bashims。

    在许多shell中有一种语法,用于在子字符串的第一次或最后一次出现处拆分字符串:

    1
    2
    3
    4
    ${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

    (这是我发表答卷的主要原因,我没有找到答案;)

    正如得分所指出的:

    # and % delete the shortest possible matching string, and

    ## and %% delete the longest possible.

    where # and ## mean from left (begin) of string, and

    % and %% meand from right (end) of string.

    这个小示例脚本在bash、dash、ksh、busybox下运行良好,在mac os的bash下也进行了测试:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    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>]

    玩得高兴!

    • ###%%%替换具有更容易记住的解释:#%删除尽可能短的匹配字符串,##%%删除尽可能长的匹配字符串。
    • IFS=\; read -a fields <<<"$var"在换行时失败,并添加一个尾随换行。另一个解决方案删除尾随的空字段。
    • 外壳分隔符是最优雅的答案,句点。
    • 最后一个选项是否可以与其他地方设置的字段分隔符列表一起使用?例如,我的意思是将其用作shell脚本,并将字段分隔符列表作为位置参数传递。
    • 是的,在一个循环中:for sep in"#""?""@" ; do ... var="${var#*$sep}" ...


    我看到过一些引用cut命令的答案,但它们都被删除了。有点奇怪,没有人详细阐述过这一点,因为我认为这是执行此类操作的更有用的命令之一,尤其是解析定界日志文件。

    在将这个特定的示例拆分为bash脚本数组的情况下,tr可能更有效,但可以使用cut,如果您想从中间提取特定的字段,则更有效。

    例子:

    1
    2
    3
    4
    $ 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参数以独立地拉动每个字段。

    当您有一个带如下行的分隔日志文件时,这会更有用:

    1
    2015-04-27|12345|some action|an attribute|meta data

    cut非常方便,可以使用cat这个文件并选择一个特定的字段进行进一步处理。

    • 因为使用了cut,这是一个合适的工具!比那些空壳黑客都清楚。
    • 只有提前知道元素的数量,这种方法才有效;您需要围绕它编程更多的逻辑。它还为每个元素运行一个外部工具。
    • 我在寻找避免csv中出现空字符串的方法。现在我也可以精确地指出"列"值。与已经在循环中使用的IFS一起工作。比我想象的要好。


    这对我很有用:

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

    • CUT只使用一个字符作为分隔符。
    • 虽然它只使用一个字符分隔符,但这正是OP所要查找的(用分号分隔的记录)。
    • 这是最好的答案。


    这种方法怎么样:

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

    来源

    • 1 +……但就不会"阵列"the variable name…我想peev宠物。良好的解决方案。
    • 1 +……但是,"集"是不必要的民族-和宣布。你不能只是IFS";" && Array=($IN)阱have used as
    • 注:+ 1侧不应该只在它recommendable to keep the old IFS,然后恢复它?(as shown by stefanb着陆在他的edit3)人(有时只是在这里和在pasting Copying about this might not)解决方案)
    • 1:第一,"that most of the is right ATA命令在这给你什么。第二,它uses to form the word分裂阵列,和不给你任何inhibit全球化扩张when to做(如果你已经知道我在任何全球性characters of the阵列元素,这些元素是replaced与匹配的文件名)。
    • 用途:IN=$'bla@some.com;john@home.com;bet 'to $'...'蓝晶石。我会与newline echo"${Array[2]}"打印字符串。在这家neccessary set --"$IN"is also。是的,to the solution防止全球化扩张,包括set -fshould。


    1
    2
    3
    4
    echo"bla@some.com;john@home.com" | sed -e 's/;/
    /g'

    bla@some.com
    john@home.com

    • -1如果字符串包含空格怎么办?例如,在这种情况下,IN="this is first line; this is second line" arrIN=( $( echo"$IN" | sed -e 's/;/
      /g' ) )
      将生成一个由8个元素组成的数组(每个单词用一个元素来分隔空格),而不是2个元素(每行用一个元素来分隔分号)
    • @Luca不,SED脚本只创建了两行。为您创建多个条目的方法是将其放入bash数组中(默认情况下在空白处拆分)。
    • 这正是关键所在:正如您在他的编辑中看到的那样,OP需要将条目存储到数组中以循环遍历它。我认为你(好)的回答没有提到使用arrIN=( $( echo"$IN" | sed -e 's/;/
      /g' ) )
      来实现这一点,也没有提到建议将ifs改为IFS=$'
      '
      ,用于那些将来降落在这里并需要拆分包含空格的字符串的人。(之后再恢复)。:)
    • @卢卡说得对。然而,当我写下这个答案时,数组分配并不在最初的问题中。


    这也适用于:

    1
    2
    3
    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。

    • 可以使用-s来避免上述问题:superuser.com/questions/896800/&hellip;"-f,-fields=list仅选择这些字段;还可以打印不包含分隔符的任何行,除非指定了-s选项。"


    我认为awk是解决你问题的最好和有效的命令。在几乎每个Linux发行版中,默认情况下,awk都包含在bash中。

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

    将给予

    1
    bla@some.com john@home.com

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

    • 或者更简单:echo"bla@some.com;john@home.com"awk'begin rs=";"print'
    • @Jaro当我有一个带逗号的字符串,需要将其重新格式化为行时,这对我来说非常有用。谢谢。
    • 它在这个场景中起作用-"echo"$split_awk-f'inode=''打印$1'"!我在尝试使用atrings("inode=")而不是字符(";")时遇到了问题。$1、$2、$3、$4设置为数组中的位置!如果有设置数组的方法…更好!谢谢!
    • @Eduardolucio,我想的是,也许你可以先把分隔符inode=替换成;,比如sed -i 's/inode\=/\;/g' your_file_to_process,然后在应用awk时定义-F';',希望能对你有所帮助。


    达伦的回答不同,我是这样做的:

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

    • 这不管用。
    • 我想是的!运行上面的命令,然后"echo$addr1…$addr2"我得到"bla@some.com…john@home.com"输出
    • 这对我很有效…我使用它在包含逗号分隔的数据库、服务器和端口数据的字符串数组上进行了匹配,以使用mysqldump。
    • 诊断:IFS=";"作业只存在于$(...; echo $IN)子壳中;这就是为什么有些读者(包括我)最初认为它不起作用的原因。我以为所有的美元都被addr1弄脏了。但尼基是对的,它确实起作用。原因是echo $IN命令使用$ifs的当前值解析其参数,然后使用空格分隔符将其回送到stdout,而不考虑$ifs的设置。因此,净效果就好像有人称之为read ADDR1 ADDR2 <<<"bla@some.com john@home.com"(注意,输入是空格分隔的,而不是分隔的)。
    • 这项工作,但$()意味着一把叉子。
    • 这在空格和换行符上失败,并且在echo $IN中使用未加引号的变量扩展来扩展通配符*
    • 我真的很喜欢这个解决方案。描述一下它的工作原理会非常有用,并使它成为一个更好的整体答案。


    在bash中,这是一种防弹方法,即使变量包含换行符,也可以使用:

    1
    IFS=';' read -d '' -ra array < <(printf '%s;\0'"$in")

    看:

    1
    2
    3
    4
    5
    6
    7
    8
    $ in=$'one;two three;*;there is
    a newline
    in 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")'

    这项工作的诀窍是使用带有空分隔符的EDOCX1(分隔符)的-d选项,以便read被强制读取其输入的所有内容。我们给read加上变量in的内容,由于printf没有后继换行。注意,我们还在printf中放置分隔符,以确保传递给read的字符串有一个尾随的分隔符。没有它,read将减少潜在的尾随空字段:

    1
    2
    3
    4
    $ 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选项来指定分隔符。因此,另一个典型的方法是:

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

    • 我发现它是该列表中罕见的能够同时与
      、spaces和*正确工作的解决方案。另外,没有循环;数组变量在执行后可以在shell中访问(与最高的上票结果相反)。注:in=$'...'不适用于双引号。我想,这需要更多的赞成票。


    如果不使用数组,那么这个单行程序怎么样:

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

    • 考虑使用read -r ...确保that for example to the characters,二,"t"输入端上茶characters as the same在你的双变量(instead of a单表字符)。
    • This is not here - 1(Ubuntu的12.04)工作。你会echo"ADDR1 $ADDR1"
      echo"ADDR2 $ADDR2"
      片断增ADDR1 bla@some.com john@home.com
      ADDR2
      (N是输出newline)
    • this is probably两involving to bug IFSstrings,固定在这里和我bash4.3。quoting $IN应该修好它。在理论$INis not(subject to,or EN expands Word通配符分裂后,quotes should be the meaning不必要的民族。即使在4.3虽然有至少一错误报告和剩余(Scheduled to be我知道保持固定quoting)好主意。
    • 在这70美元甚至if if contain newlines is quoted美元。在trailing newline adds布尔。


    这是一个干净的三衬板:

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

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

    如果在这之后还有代码,还需要恢复$IFS,例如unset IFS

    • 使用不带引号的$in允许扩展通配符。


    不设国际单项体育联合会

    如果你只有一个结肠,你可以这样做:

    1
    2
    3
    a="foo:bar"
    b=${a%:*}
    c=${a##*:}

    你会得到:

    1
    2
    b = foo
    c = bar

    下面的bash/zsh函数将其第一个参数拆分为第二个参数给定的分隔符:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    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
    }

    例如,命令

    1
    $ split 'a;b;c' ';'

    产量

    1
    2
    3
    a
    b
    c

    例如,该输出可以通过管道传输到其他命令。例子:

    1
    2
    3
    4
    $ split 'a;b;c' ';' | cat -n
    1   a
    2   b
    3   c

    与给出的其他解决方案相比,本方案具有以下优点:

    • IFS不被重写:由于偶数个局部变量的动态范围,在循环上重写IFS会导致新值泄漏到从循环内执行的函数调用中。

    • 不使用数组:使用read将字符串读取到数组中需要bash中的标志-a,zsh中的标志-a

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

    1
    2
    3
    4
    5
    6
    7
    #!/usr/bin/env bash

    split() {
        # ...
    }

    split"$@"

    • 工作,模块化整洁。


    有一种简单而聪明的方法:

    1
    echo"add:sfff" | xargs -d: -i  echo {}

    但您必须使用gnu-xargs,bsd-xargs不能支持-d delim。如果你像我一样使用苹果Mac。可以安装GNU xargs:

    1
    brew install findutils

    然后

    1
    echo"add:sfff" | gxargs -d: -i  echo {}

    这是最简单的方法。

    1
    2
    3
    4
    5
    6
    spo='one;two;three'
    OIFS=$IFS
    IFS=';'
    spo_array=($spo)
    IFS=$OIFS
    echo ${spo_array[*]}

    你可以在很多情况下使用awk

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

    你也可以用这个

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


    这里有一些很酷的答案(特别是勘误表),但是对于类似于用其他语言拆分的内容——这就是我认为最初的问题的意思——我已经解决了这个问题:

    1
    2
    IN="bla@some.com;john@home.com"
    declare -a a="(${IN/;/ })";

    现在,${a[0]}${a[1]}等,如您所料。用${#a[*]}表示术语数。或者迭代,当然:

    1
    for i in ${a[*]}; do echo $i; done

    重要注意事项:

    这在没有空间担心的情况下是有效的,解决了我的问题,但可能无法解决你的问题。在这种情况下,使用$IFS解决方案。

    • IN包含两个以上的电子邮件地址时不工作。请在佩林德罗姆的回答中引用相同的观点(但是固定的)。
    • 最好使用${IN//;/ }(双斜线),使其也可以使用两个以上的值。注意任何通配符(*?[将被扩展。后面的空字段将被丢弃。


    1
    2
    3
    4
    5
    6
    7
    IN="bla@some.com;john@home.com"
    IFS=';'
    read -a IN_arr <<<"${IN}"
    for entry in"${IN_arr[@]}"
    do
        echo $entry
    done

    产量

    1
    2
    bla@some.com
    john@home.com

    系统:Ubuntu 12.04.1

    • IFS并没有在read的特定上下文中设置,因此它可能会扰乱其他代码(如果有的话)。


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

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

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

    两种不需要bash数组的Bourne-ish替代方案:

    案例1:保持简单明了:使用换行符作为记录分隔符…如。

    1
    2
    3
    4
    5
    6
    7
    IN="bla@some.com
    john@home.com"


    while read i; do
      # process"$i" ... eg.
        echo"[email:$i]"
    done <<<"$IN"

    注意:在第一种情况下,没有分支子流程来协助列表操作。

    想法:也许在内部广泛使用nl是值得的,并且在外部生成最终结果时,只需转换为不同的rs。

    案例2:使用";"作为记录分隔符…如。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    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"

    在这两种情况下,子列表都可以在循环完成后在循环内持久化。这在处理内存中的列表时非常有用,而不是将列表存储在文件中。P.S.保持冷静,继续b-)


    1
    2
    3
    4
    5
    6
    7
    8
    9
    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

    输出:

    1
    2
    3
    4
    5
    bla@some.com
    john@home.com
    Charlie Brown <cbrown@acme.com
    !"#$%&/()[]{}*? are no problem
    simple is beautiful :-)

    解释:使用括号()的简单赋值将分号分隔的列表转换为数组,前提是在执行此操作时具有正确的IFS。循环的标准像往常一样处理该数组中的单个项。请注意,为in变量提供的列表必须是"硬"引用的,也就是说,只有一个勾号。

    必须保存和恢复ifs,因为bash不将赋值视为命令。另一种解决方法是将赋值包装在函数中,并使用修改后的ifs调用该函数。在这种情况下,不需要单独保存/恢复IFS。感谢"bize"指出这一点。

    • !"#$%&/()[]{}*? are no problem嗯…不完全是:[]*?是全局字符。那么,创建这个目录和文件怎么样:"mkdir"!"#$%&;';触摸'!"#$%&;/()[]明白了,哈哈哈-没问题"并运行您的命令?简单可能是美丽的,但当它破碎时,它就破碎了。
    • @gniurf gniurf字符串存储在变量中。请看原始问题。
    • @你没有完全理解我的评论。进入一个临时目录并发出这些命令:mkdir '!"#$%&'; touch '!"#$%&/()[]{} got you hahahaha - are no problem'。我必须承认,他们只会创建一个目录和一个文件,名字看起来很奇怪。然后用你给的精确的IN来运行你的命令:IN='bla@some.com;john@home.com;Charlie Brown 。你会发现你不会得到你期望的输出。因为您使用了一个受路径名扩展约束的方法来拆分字符串。
    • 这是为了证明字符*?[...],即使设置了extglob!(...)@(...)?(...)+(...)都是这种方法的问题!
    • 另一个反对你的道路方法的论据是:如果有人在nullglobfailglob集合中使用这种方法,将会有一些惊喜!您可以尝试一下:使用shopt -s nullglobshopt -s failglob运行代码。
    • @感谢您对环球网的详细评论。我调整了代码使之变为全局。不过,我的观点只是想说明,相当简单的任务可以完成拆分工作。


    除了已经提供的奇妙答案外,如果只是打印出数据的问题,您可以考虑使用awk

    1
    2
    awk -F";" '{for (i=1;i<=NF;i++) printf("> [%s]
    ", $i)}'
    <<<"$IN"

    这将字段分隔符设置为;,以便它可以使用for循环遍历字段并相应地打印。

    试验

    1
    2
    3
    4
    5
    $ IN="bla@some.com;john@home.com"
    $ awk -F";" '{for (i=1;i<=NF;i++) printf("> [%s]
    ", $i)}'
    <<<"$IN"
    > [bla@some.com]
    > [john@home.com]

    使用其他输入:

    1
    2
    3
    4
    5
    6
    7
    $ awk -F";" '{for (i=1;i<=NF;i++) printf("> [%s]
    ", $i)}'
    <<<"a;b;c   d;e_;f"
    > [a]
    > [b]
    > [c   d]
    > [e_]
    > [f]

    在Android Shell中,大多数建议的方法都不起作用:

    1
    2
    $ IFS=':' read -ra ADDR <<<"$PATH"                            
    /system/bin/sh: can't create temporary file /sqlite_stmt_journals/mksh.EbNoR10629: No such file or directory

    工作内容是:

    1
    2
    3
    4
    5
    6
    $ for i in ${PATH//:/ }; do echo $i; done
    /sbin
    /vendor/bin
    /system/sbin
    /system/bin
    /system/xbin

    其中,//表示全球替代。

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


    好吧,伙计们!

    这是我的答案!

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    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
    ", $i}}'
    <<<"${F_ABOUT_DISTRO_R}")
    while read -r line; do
       SPLIT+=("$line")
    done <<<"$SPLIT_NOW"
    for i in"${SPLIT[@]}"; do
        echo"$i"
    done

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

    因为两个原因:

  • 您不需要转义分隔符;
  • 您不会对空格有问题。该值将在数组中正确分隔!
  • []

    • fyi、/etc/os-release/etc/lsb-release都是源代码,而不是解析的。所以你的方法是错误的。此外,您还不能完全回答有关在分隔符上拆分字符串的问题。


    使用set内置加载$@数组:

    1
    2
    3
    IN="bla@some.com;john@home.com"
    IFS=';'; set $IN; IFS=$' \t
    '

    然后,让聚会开始:

    1
    2
    3
    echo $#
    for a; do echo $a; done
    ADDR1=$1 ADDR2=$2

    • 最好使用set -- $IN,以避免出现以dash开头的"$in"问题。不过,未经引用的$IN的扩展将扩展通配符(*?[)。


    这甚至可以处理空白:

    1
    IFS=';' read ADDR1 ADDR2 <<< $(echo ${IN})

    将由";"分隔的字符串拆分为数组的一行程序是:

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

    这只在子shell中设置ifs,因此您不必担心保存和恢复其值。

    • -1这里不适用(Ubuntu 12.04)。它只打印第一个回声,其中包含所有$in值,而第二个回声为空。如果您将echo"0:"$addrs[0] echo"1:"$addrs[1]输出is0: bla@some.com;john@home.com
      1:
      (是新行),就可以看到它。
    • 请参阅nickjb的回答,以获取此想法的工作替代方案stackoverflow.com/a/6583589/1032370
    • - 1, 1。IFS没有被设置在那个子shell中(它被传递到"echo"环境中,这是一个内置的,所以无论如何都不会发生任何事情)。2。$IN的报价不受IFS拆分的影响。三。进程替换被空白分割,但这可能会损坏原始数据。


    也许不是最优雅的解决方案,但适用于*和spaces:

    1
    2
    3
    4
    5
    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

    输出

    1
    2
    3
    > [bla@so me.com]
    > [*]
    > [john@home.com]

    其他示例(开始和结束处的分隔符):

    1
    2
    3
    4
    5
    6
    IN=";bla@so me.com;*;john@home.com;"
    > []
    > [bla@so me.com]
    > [*]
    > [john@home.com]
    > []

    基本上,它删除除;之外的所有字符,使delims例如;;;。然后按${#delims}的计数,从1number-of-delimiters进行for循环。最后一步是使用cut安全地获得$i第部分。


    它为我工作:

    echo $PATH | ruby -ne 'puts $_.split(":")'


    又一个迟来的答案…如果你是Java意识的,这里是BasHJ(HTTPS://SooSurfGe.NET/PrimeSt/BasHJ/)解决方案:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    #!/usr/bin/bashj

    #!java

    private static String[] cuts;
    private static int cnt=0;
    public static void split(String words,String regexp) {cuts=words.split(regexp);}
    public static String next() {return(cnt<cuts.length ? cuts[cnt++] :"null");}

    #!bash

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

    : j.split($IN,";")    # java method call

    while true
    do
        NAME=j.next()     # java method call
        if [ $NAME != null ] ; then echo $NAME ; else exit ; fi
    done

    有两种简单的方法:

    1
    2
    cat"text1;text2;text3" | tr"""
    "

    1
    2
    cat"text1;text2;text3" | sed -e 's/ /
    /g'

    • S/CAT/ECHO/G夏利米特
    • -1错误:cat:text1;text2;text3:没有这样的文件或目录
    • 我想你把catecho搞混了。cat从文件中读取。echo读取给出的文本。