【问题标题】:How do I split a string on a delimiter in Bash?如何在 Bash 的分隔符上拆分字符串?
【发布时间】:2010-10-29 11:12:06
【问题描述】:

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

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 周围没有括号,它可以工作。

【问题讨论】:

  • 关于您的“Edit2”:您可以简单地“取消设置 IFS”,它将返回到默认状态。除非您有理由期望它已被设置为非默认值,否则无需显式保存和恢复它。此外,如果您在函数内部执行此操作(如果不是,为什么不这样做?),您可以将 IFS 设置为局部变量,一旦您退出函数,它将返回到之前的值。
  • @BrooksMoses:(a) +1 尽可能使用local IFS=...; (b) -1 表示unset IFS,这并不完全将 IFS 重置为其默认值,尽管我相信未设置的 IFS 的行为与 IFS 的默认值 ($'\t\n') 相同,但它似乎盲目假设您的代码永远不会在 IFS 设置为自定义值的情况下被调用,这是一种不好的做法; (c) 另一个想法是调用子shell:(IFS=$custom; ...) 当子shell 退出时,IFS 将返回到原来的样子。
  • 我只是想快速查看一下路径以决定在哪里抛出可执行文件,所以我求助于运行ruby -e "puts ENV.fetch('PATH').split(':')"。如果您想保持纯 bash 将无济于事,但使用具有内置拆分的任何脚本语言会更容易。
  • for x in $(IFS=';';echo $IN); do echo "> [$x]"; done
  • 为了将其保存为数组,我必须放置另一组括号并将\n 更改为一个空格。所以最后一行是mails=($(echo $IN | tr ";" " "))。所以现在我可以通过使用数组符号mails[index] 或只是在循环中迭代来检查mails 的元素

标签: bash shell split scripting


【解决方案1】:

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

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

您可以使用这种循环来初始化数组,但可能有更简单的方法。

【讨论】:

  • 您应该保留 IFS 答案。它教会了我一些我不知道的东西,而且它确实是一个数组,而这只是一个廉价的替代品。
  • 我明白了。是的,我发现做这些愚蠢的实验,每次我试图回答问题时,我都会学习新事物。我根据#bash IRC 反馈编辑了一些东西,但没有删除:)
  • 您可以将其更改为 echo "$IN" | tr';' '\n' |同时读取-r ADDY; # 处理“$ADDY”;我认为这样做是为了让他幸运:) 请注意,这将分叉,并且您不能从循环内更改外部变量(这就是我使用
  • 总结 cmets 中的争论:一般使用注意事项:shell 将 分词扩展 应用于可能不需要的字符串;试试吧。 IN="bla@some.com;john@home.com;*;broken apart"。简而言之:如果您的标记包含嵌入的空格和/或字符,这种方法将失效。例如 * 恰好使令牌匹配当前文件夹中的文件名。
  • 这是非常有帮助的答案。例如IN=abc;def;123。我们如何也打印索引号? echo $count $i ?
【解决方案2】:
echo "bla@some.com;john@home.com" | sed -e 's/;/\n/g'
bla@some.com
john@home.com

【讨论】:

  • -1 如果字符串包含空格怎么办? 例如IN="this is first line; this is second line" arrIN=( $( echo "$IN" | sed -e 's/;/\n/g' ) ) 在这种情况下将生成一个包含 8 个元素的数组(每个单词空格分隔一个元素),而不是大于 2(每行一个元素用分号隔开)
  • @Luca 不,sed 脚本恰好创建了两行。为您创建多个条目的原因是当您将其放入 bash 数组(默认情况下在空白处拆分)
  • 这正是重点:OP 需要将条目存储到一个数组中以循环遍历它,正如您在他的编辑中看到的那样。我认为您的(好的)答案没有提到使用arrIN=( $( echo "$IN" | sed -e 's/;/\n/g' ) ) 来实现这一目标,并建议将 IFS 更改为IFS=$'\n' 以供将来登陆并需要拆分包含空格的字符串的人使用。 (并在之后恢复它)。 :)
  • @Luca 好点。但是,当我写下那个答案时,数组分配不在最初的问题中。
【解决方案3】:

你可以设置internal field separator(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"

【讨论】:

  • 这可能是最好的方法。 IFS 将保持其当前值多长时间,它是否会在不应该设置的时候设置我的代码,以及当我完成后如何重置它?
  • 现在在应用修复后,仅在读取命令的持续时间内:)
  • 你可以在不使用 while 循环的情况下一次读取所有内容: read -r -d '' -a addr
  • @LucaBorrione 将IFS 设置在与read 相同的行上,不带分号或其他分隔符,而不是在单独的命令中,将其范围限定为该命令——所以它总是“恢复” ";您无需手动执行任何操作。
  • @imagineerThis 存在一个涉及此处字符串和 IFS 本地更改的错误,需要引用 $IN。该错误已在bash 4.3 中修复。
【解决方案4】:

这个方法怎么样:

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

Source

【讨论】:

  • +1 ...但我不会将变量命名为“Array” ...我猜是宠物。很好的解决方案。
  • +1 ...但“设置”和声明 -a 是不必要的。你也可以只使用IFS";" &amp;&amp; Array=($IN)
  • +1 附注:不应该建议保留旧的 IFS 然后恢复它吗? (如 stefanB 在他的编辑 3 中所示)登陆这里的人(有时只是复制和粘贴解决方案)可能不会考虑这个
  • -1:首先,@ata 是正确的,其中的大多数命令什么都不做。其次,它使用分词来形成数组,并且在这样做时不做任何事情来抑制全局扩展(因此,如果您在任何数组元素中有全局字符,这些元素将被替换为匹配的文件名)。
  • 建议使用$'...':IN=$'bla@some.com;john@home.com;bet &lt;d@\ns* kl.com&gt;'。然后echo "${Array[2]}" 将打印一个带有换行符的字符串。在这种情况下,set -- "$IN" 也是必需的。是的,为防止全局扩展,解决方案应包含set -f
【解决方案5】:

如果你不使用数组,那么这个衬里怎么样:

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

【讨论】:

  • 考虑使用read -r ... 以确保,例如,输入中的两个字符“\t”最终与变量中的两个字符相同(而不是单个制表符)。
  • -1 这在这里不起作用(ubuntu 12.04)。将echo "ADDR1 $ADDR1"\n echo "ADDR2 $ADDR2" 添加到您的sn-p 将输出ADDR1 bla@some.com john@home.com\nADDR2(\n 是换行符)
  • 这可能是由于涉及IFS 的错误以及此处在bash 4.3 中修复的字符串。引用 $IN 应该可以解决它。 (理论上,$IN 在扩展后不会进行分词或通配,这意味着引号应该是不必要的。不过,即使在 4.3 中,至少还有一个错误 - 已报告并计划修复 - 所以引用仍然是个好主意。)
  • 如果 $in 包含换行符,即使引用了 $IN 也会中断。并添加一个尾随换行符。
  • 这个问题和许多其他解决方案也是假设 $IN 中正好有两个元素 - 或者您愿意将第二个和后续项目在 ADDR2 中粉碎在一起。我知道这符合要求,但这是一个定时炸弹。
【解决方案6】:

取自Bash shell script split array

IN="bla@some.com;john@home.com"
arrIN=(${IN//;/ })
echo ${arrIN[1]}                  # Output: john@home.com

解释:

此构造将字符串IN 中所有出现的';'(初始// 表示全局替换)替换为' '(单个空格),然后将空格分隔的字符串解释为数组(即括号的作用)。

在大括号内使用';' 字符替换每个' ' 字符的语法称为Parameter Expansion

有一些常见的陷阱:

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

【讨论】:

  • 我只想补充:这是最简单的,你可以使用 ${arrIN[1]} 访问数组元素(当然是从零开始)
  • 找到它:在 ${} 中修改变量的技术被称为“参数扩展”。
  • 不,当还有空格时,我认为这不起作用......它将“,”转换为“”,然后构建一个空格分隔的数组。
  • 非常简洁,但有一般用途的注意事项:shell 对字符串应用分词扩展 ,这可能是不希望的;试试吧。 IN="bla@some.com;john@home.com;*;broken apart"。简而言之:如果您的标记包含嵌入的空格和/或字符,这种方法将失效。例如 * 恰好使令牌匹配当前文件夹中的文件名。
  • 由于其他原因,这是一种不好的方法:例如,如果您的字符串包含;*;,那么* 将扩展为当前目录中的文件名列表。 -1
【解决方案7】:

Darron's answer 的不同看法,我就是这样做的:

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

【讨论】:

  • 我认为可以!运行上面的命令,然后“echo $ADDR1 ... $ADDR2”,我得到“bla@some.com ... john@home.com”输出
  • 这对我来说真的很有效......我用它来遍历包含逗号分隔的 DB、SERVER、PORT 数据的字符串数组以使用 mysqldump。
  • 诊断:IFS=";" 赋值仅存在于$(...; echo $IN) 子shell 中;这就是为什么一些读者(包括我)最初认为它不起作用的原因。我假设所有的 $IN 都被 ADDR1 吞噬了。但是 nickjb 是正确的;它确实有效。原因是echo $IN 命令使用 $IFS 的当前值解析其参数,然后使用空格分隔符将它们回显到标准输出,而不管 $IFS 的设置如何。所以最终效果就好像有人调用了read ADDR1 ADDR2 &lt;&lt;&lt; "bla@some.com john@home.com"(注意输入是空格分隔的,而不是;-分隔的)。
  • 这在空格和换行符上会失败,并且还会在 echo $IN 中使用不带引号的变量扩展来扩展通配符 *
  • 我真的很喜欢这个解决方案。对其工作原理的描述将非常有用,并使其成为更好的整体答案。
【解决方案8】:

这是最简单的方法。

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

【讨论】:

    【解决方案9】:

    有两种简单的方法:

    cat "text1;text2;text3" | tr " " "\n"
    

    cat "text1;text2;text3" | sed -e 's/ /\n/g'
    

    【讨论】:

    • 我认为您对catecho 感到困惑。 cat 从文件中读取。 echo 读取给定的文本。
    【解决方案10】:

    用';'分隔字符串的单行符放入一个数组是:

    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]}\n echo "1: "${ADDRS[1]} 你可以看到它的输出是0: bla@some.com;john@home.com\n 1: (\n 是新行)
    • 请参阅 nickjb 的回答,以获取此想法的可行替代方案 stackoverflow.com/a/6583589/1032370
    • -1, 1. IFS 没有在那个子shell中设置(它被传递到“echo”的环境,这是一个内置的,所以无论如何什么都没有发生)。 2. $IN 被引用,因此不受 IFS 拆分的影响。 3.进程替换被空格分割,但这可能会破坏原始数据。
    【解决方案11】:

    这也有效:

    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/… "-f, --fields=LIST 仅选择这些字段;也打印任何不包含分隔符的行,除非 -s选项已指定”
    【解决方案12】:

    这里有一些很酷的答案(尤其是错误的),但是对于类似于其他语言中的拆分的东西——这就是我最初的问题的意思——我决定这样做:

    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's answer 参考相同的想法(但已修复)
    • 更好地使用${IN//;/ }(双斜杠)使其也适用于两个以上的值。请注意,任何通配符 (*?[) 都会被扩展。并且后面的空字段将被丢弃。
    【解决方案13】:

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

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

    会给

    bla@some.com john@home.com
    

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

    【讨论】:

    • 或者更简单:echo "bla@some.com;john@home.com" | awk 'BEGIN{RS=";"} {打印}'
    • @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,然后在应用awk 时定义-F';',希望对您有所帮助。
    【解决方案14】:

    兼容的答案

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

    但是,首先要注意bash 有许多特殊 功能(所谓的bashisms)在任何其他@987654323 中都不起作用,这一点很重要@。

    特别是 arraysassociative arrayspattern substitution,它们用于本文的解决方案以及其他线程是 bashisms,可能无法在许多人使用的其他 shell 下工作。

    例如:在我的 Debian GNU/Linux 上,有一个名为 标准 shell;我知道很多人喜欢使用另一个叫做 的shell;还有一个特殊的工具叫做,带有他自己的shell解释器()。

    请求的字符串

    上题中要拆分的字符串是:

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

    我将使用此字符串的修改版本来确保我的解决方案对包含空格的字符串具有鲁棒性,这可能会破坏其他解决方案:

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

    根据(版本>=4.2)中的分隔符拆分字符串

    pure bash 中,我们可以创建一个 array,其中元素被 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>]
    

    或者您可以在使用 shifting 方法处理后从数组中删除每个字段,我喜欢这种方法:

    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.
    

    或者你可以使用&lt;&lt;&lt;,并在函数体中包含一些处理来删除它添加的换行符:

    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 中都有一种语法用于从模式的 firstlast 出现中删除字符串的子字符串。请注意,* 是一个通配符,代表零个或多个字符:

    (到目前为止发布的任何解决方案都缺乏这种方法是我写这个答案的主要原因;)

    ${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所解释的:

    #%分别从字符串的startend中删除可能的最短匹配子字符串,并且

    ##%% 删除可能最长的匹配子串。

    使用上述语法,我们可以创建一种方法,通过删除分隔符之前或之后的子字符串,从字符串中提取子字符串“元素”。

    下面的代码块在(包括Mac OS 的bash)、 中运行良好:

    (感谢Adam Katzcomment,让这个循环变得简单多了!)

    IN="bla@some.com;john@home.com;Full Name <fulnam@other.org>"
    while [ "$IN" != "$iter" ] ;do
        # extract the substring from start of string up to delimiter.
        iter=${IN%%;*}
        # delete this first "element" AND next separator, from $IN.
        IN="${IN#$iter;}"
        # Print (or doing anything with) the first "element".
        echo "> [$iter]"
    done
    # > [bla@some.com]
    # > [john@home.com]
    # > [Full Name <fulnam@other.org>]
    

    玩得开心!

    【讨论】:

    • ###%%% 替换具有 IMO 更容易记住的解释(它们删除了多少):#%删除尽可能短的匹配字符串,##%% 删除尽可能长的匹配字符串。
    • IFS=\; read -a fields &lt;&lt;&lt;"$var" 在换行符上失败并添加尾随换行符。另一种解决方案删除了​​一个尾随的空字段。
    • 这个答案非常史诗。
    • 如果您将可移植 shell 答案的 while 条件更改为 [ "$IN" != "$iter" ],您将不需要最后的条件,只需要它的 else 子句。整个循环可以浓缩为两条内线:while [ "$IN" != "$iter" ]; do iter="${IN%%;*}" IN="${IN#*;}"; echo "&gt; [$iter]"; done
    • @AdamKatz 非常聪明,答案已编辑,谢谢!
    【解决方案15】:

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

    IN="bla@some.com;john@home.com"
    arr=(`echo $IN | tr ';' ' '`)
    
    echo ${arr[0]}
    echo ${arr[1]}
    

    【讨论】:

      【解决方案16】:

      使用内置的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 的未引用扩展将扩展通配符 (*?[)。
      【解决方案17】:

      两个都不需要 bash 数组的 bourne-ish 替代方案:

      案例 1:保持简洁:使用 NewLine 作为记录分隔符...例如。

      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"
      

      在这两种情况下,可以在循环中组成的子列表在循环完成后是持久的。这在操作内存中的列表时很有用,而不是将列表存储在文件中。 {附注保持冷静并继续 B-) }

      【讨论】:

        【解决方案18】:

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

        这个工作的诀窍是使用read(分隔符)的-d选项和一个空分隔符,这样read就被强制读取它输入的所有内容。我们用变量in 的内容提供read,由于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(又名 readarray)支持 -d 选项来指定分隔符。因此,另一种规范的方式是:

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

        【讨论】:

        • 我发现它是该列表中罕见的解决方案,可以同时与\n、空格和* 一起正常工作。此外,没有循环;执行后可以在 shell 中访问数组变量(与最高投票的答案相反)。请注意,in=$'...',它不适用于双引号。我认为,它需要更多的支持。
        • 如果我想使用 % 作为分隔符,mapfile 示例将失败。我建议printf '%s' "$in%"
        【解决方案19】:
        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”指出这一点。

        【讨论】:

        • !"#$%&amp;/()[]{}*? are no problem 好吧......不完全是:[]*? 是全局字符。那么如何创建这个目录和文件: `mkdir '!"#$%&'; touch '!"#$%&/()[]{} got you haha​​haha - are no problem' 并运行你的命令?简单也许是美好的,但当它坏了,它就坏了。
        • @gniourf_gniourf 字符串存储在一个变量中。请参阅原始问题。
        • @ajaaskel 你没有完全理解我的评论。进入临时目录并发出以下命令:mkdir '!"#$%&amp;'; touch '!"#$%&amp;/()[]{} got you hahahaha - are no problem'。他们只会创建一个目录和一个文件,名字看起来很奇怪,我必须承认。然后使用您提供的确切 IN 运行您的命令:IN='bla@some.com;john@home.com;Charlie Brown &lt;cbrown@acme.com;!"#$%&amp;/()[]{}*? are no problem;simple is beautiful :-)'。你会发现你不会得到你期望的输出。因为您正在使用一种受路径名扩展约束的方法来拆分您的字符串。
        • 这是为了证明字符*?[...],甚至如果设置了extglob!(...)@(...)?(...)、@987654335 @这个方法有问题!
        • @gniourf_gniourf 感谢有关 globbing 的详细 cmets。我调整了代码以消除通配符。然而,我的观点只是为了表明相当简单的分配可以完成拆分工作。
        【解决方案20】:

        除了已经提供的精彩答案之外,如果只是打印出您可能考虑使用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]
        

        【讨论】:

          【解决方案21】:

          在 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
          

          其中// 表示全局替换。

          【讨论】:

          • 如果 $PATH 的任何部分包含空格(或换行符)则失败。还扩展通配符(星号 *、问号 ? 和大括号 [...])。
          【解决方案22】:

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

          【讨论】:

          • 感谢使用cut,它是工作的正确工具!比任何那些 shell hack 都清楚。
          • 这种方法只有在事先知道元素个数的情况下才有效;您需要围绕它编写更多逻辑。它还为每个元素运行一个外部工具。
          • 我非常想避免在 csv 中出现空字符串。现在我也可以指出确切的“列”值。使用已在循环中使用的 IFS。比我预期的要好。
          • 对于提取 ID 和 PID 也非常有用,即
          • 这个答案值得向下滚动半页:)
          【解决方案23】:

          这是一个干净的 3-liner:

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

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

          如果你之后有任何代码,你还需要恢复$IFS,例如unset IFS.

          【讨论】:

          • $in unquoted 的使用允许扩展通配符。
          【解决方案24】:

          有这样一个简单而聪明的方法:

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

          【讨论】:

            【解决方案25】:

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

            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,例如。 ;;;。然后它从1 循环fornumber-of-delimiters,由${#delims} 计数。最后一步是使用cut 安全地获取$ith 部分。

            【讨论】:

              【解决方案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\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 旨在获取来源,而不是解析。所以你的方法真的是错误的。此外,您并没有完全回答关于在分隔符上分割字符串的问题。
              【解决方案27】:

              不设置 IFS

              如果你只有一个冒号,你可以这样做:

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

              你会得到:

              b = foo
              c = bar
              

              【讨论】:

                【解决方案28】:

                这对我有用:

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

                【讨论】:

                • 虽然它只适用于单个字符分隔符,但这正是 OP 正在寻找的(由分号分隔的记录)。
                • 大约四年前由@Ashok 回答,并且一年多前由@DougW 回答,比您的回答更多信息。请发布与其他人不同的解决方案。
                • 这是最简洁易懂的cut例子imo。
                【解决方案29】:
                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

                【讨论】:

                • IFS 没有在 read 的特定上下文中设置,因此它可能会扰乱其余代码(如果有)。
                【解决方案30】:

                以下 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 将字符串读入数组需要 Bash 中的标志 -a 和 zsh 中的 -A

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

                #!/usr/bin/env bash
                
                split() {
                    # ...
                }
                
                split "$@"
                

                【讨论】:

                • 似乎不适用于长度超过 1 个字符的分隔符:split=$(split "$content" "file://")
                • 真 - 来自help read-d delim continue until the first character of DELIM is read, rather than newline
                猜你喜欢
                • 1970-01-01
                • 2017-04-02
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2011-10-03
                • 1970-01-01
                • 1970-01-01
                • 2021-06-04
                相关资源
                最近更新 更多