ChatGPT解决这个技术问题 Extra ChatGPT

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

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

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)在可能的情况下使用 local IFS=... +1; (b) unset IFS 为 -1,这并不完全将 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 的元素

r
robe007

您可以设置 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 <<< "$in" # -d '' 是这里的关键,它告诉 read 不要在第一个换行符处停止(这是默认的 -d),但会一直持续到 EOF 或 NULL 字节(仅出现在二进制数据中)。
@LucaBorrione 将 IFS 设置在与 read 相同的行上,没有分号或其他分隔符,而不是在单独的命令中,将其范围限定为该命令 - 所以它总是“恢复”;您无需手动执行任何操作。
@imagineerThis 存在一个涉及此处字符串和 IFS 本地更改的错误,需要引用 $IN。该错误已在 bash 4.3 中修复。
a
amo-ej1

取自 Bash shell script split array

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

解释:

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

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

有一些常见的陷阱:

如果原始字符串有空格,则需要使用 IFS:

IFS=':'; arrIN=($IN);取消设置 IFS;

如果原始字符串有空格并且分隔符是新行,则可以使用以下方式设置 IFS:

IFS=$'\n'; arrIN=($IN);取消设置 IFS;


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

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

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

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


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

我已经看到几个引用 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 都清楚。
这种方法只有在您事先知道元素数量的情况下才有效;您需要围绕它编写更多逻辑。它还为每个元素运行一个外部工具。
Excatly waht 我一直在寻找试图避免 csv 中的空字符串。现在我也可以指出确切的“列”值。使用已在循环中使用的 IFS。比我预期的要好。
对于提取 ID 和 PID 也非常有用,即
这个答案值得向下滚动半页:)
F
F. Hauri - Give Up GitHub

兼容的答案

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

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

特别是,在本文的解决方案以及线程中的其他解决方案中使用的数组、关联数组和模式替换是 bashism,可能无法在许多人使用的其他 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>"

根据 bash 中的分隔符拆分字符串(版本 >=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 是进行拆分的最快 方式,因为没有调用 forks 或外部资源。

定义数组后,您可以使用一个简单的循环来处理每个字段(或者,更确切地说,处理您现在定义的数组中的每个元素):

# `"${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>]

更新:最近的 bash >= 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.

根据shell中的分隔符拆分字符串

如果您不能使用 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 所解释的:

# 和 % 分别从字符串的开头和结尾删除尽可能短的匹配子串,## 和 %% 删除尽可能长的匹配子串。

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

下面的代码块在 (包括 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 <<<"$var" 在换行符上失败并添加尾随换行符。另一个解决方案删除了一个尾随的空字段。
这个答案非常史诗。
如果您将可移植 shell 答案的 while 条件更改为 [ "$IN" != "$iter" ],您将不需要最后的条件,只需要它的 else 子句。整个循环可以压缩为两条内线:while [ "$IN" != "$iter" ]; do iter="${IN%%;*}" IN="${IN#*;}"; echo "> [$iter]"; done
@AdamKatz 非常聪明,答案已编辑,谢谢!
l
lfender6445

这对我有用:

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

尽管它仅适用于单个字符分隔符,但这正是 OP 正在寻找的(由分号分隔的记录)。
大约四年前由 @Ashok 和一年多前由 @DougW 回答,比您的回答提供了更多信息。请发布与其他人不同的解决方案。
这是 imo 最简洁易懂的 cut 示例。
正如 shellcheck.net 很容易揭示的那样,由于缺少引用,这将在某些输入字符串上中断。另请参阅 When to wrap quotes around a shell variable(秘密 TLDR:基本上总是如此,至少在您了解何时可以甚至应该省略引号之前)。
n
noamtm

我认为 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';',希望对您有所帮助。
B
BLeB

这种方法怎么样:

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";" && Array=($IN)
+1仅附注:是否建议保留旧的 IFS 然后恢复它? (如 stefanB 在他的 edit3 中所示)登陆这里的人(有时只是复制和粘贴解决方案)可能不会考虑这个
-1:首先,@ata 是正确的,其中的大多数命令什么都不做。其次,它使用分词来形成数组,并且在这样做时不做任何事情来抑制全局扩展(因此,如果您在任何数组元素中有全局字符,这些元素将被替换为匹配的文件名)。
建议使用 $'...'IN=$'bla@some.com;john@home.com;bet <d@\ns* kl.com>'。然后 echo "${Array[2]}" 将打印一个带有换行符的字符串。在这种情况下,set -- "$IN" 也是必需的。是的,为防止全局扩展,解决方案应包括 set -f
l
lothar
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 好点。但是,当我写下那个答案时,数组分配不在最初的问题中。
B
Boris S.

这也有效:

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 选项"
C
Community

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 <<< "bla@some.com john@home.com" (注意输入是空格分隔的,而不是 ;-分隔的)。
这在空格和换行符上失败,并且在 echo $IN 中使用不带引号的变量扩展来扩展通配符 *
我真的很喜欢这个解决方案。对其工作原理的描述将非常有用,并使其成为更好的整体答案。
D
Darron

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

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

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

在 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%"
E
Emilien Brigand

不设置 IFS

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

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

你会得到:

b = foo
c = bar

k
kenorb

这是一个干净的 3 衬里:

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 可以扩展通配符。
H
Halle Knast

以下 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
V
Victor Choy

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

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

s
shuaihanhungry

您可以将 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"

g
ghost

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

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

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

J
James Andino

这是最简单的方法。

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

B
Benjamin W.

这里有一些很酷的答案(尤其是错误的),但是对于类似于在其他语言中拆分的东西——这就是我认为原始问题的意思——我决定了这一点:

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//;/ }(双斜杠)使其也适用于两个以上的值。请注意,任何通配符 (*?[) 都会被扩展。一个尾随的空字段将被丢弃。
r
rashok
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 的特定上下文中设置,因此它可能会扰乱其余代码(如果有)。
j
jeberle

使用 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 扩展将扩展通配符 (*?[)。
N
NevilleDNZ

两个都不需要 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"

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


f
fedorqui

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

P
Peter Mortensen

在 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 的任何部分包含空格(或换行符),则失败。还扩展通配符(星号 *、问号 ? 和大括号 [...])。
P
Peter Mortensen
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 '!"#$%&'; touch '!"#$%&/()[]{} 让你哈哈哈哈——没问题'并运行你的命令?简单也许是美好的,但当它破碎时,它就破碎了。
@gniourf_gniourf 字符串存储在变量中。请参阅原始问题。
@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 :-)'。你会发现你不会得到你期望的输出。因为您正在使用一种受路径名扩展影响的方法来拆分您的字符串。
这是为了证明字符 *?[...] 甚至,如果设置了 extglob,则 !(...)@(...)?(...)+(...) 这种方法有问题!
@gniourf_gniourf 感谢您对 globbing 的详细评论。我调整了代码以消除通配符。然而,我的观点只是为了表明相当简单的分配可以完成拆分工作。
t
tripleee

这是我的答案!

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

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

因为两个原因:

您不需要转义分隔符;你不会有空格的问题。该值将在数组中正确分隔。


仅供参考,/etc/os-release/etc/lsb-release 旨在获取来源,而不是解析。所以你的方法真的是错误的。此外,您并没有完全回答关于 在分隔符上分割字符串的问题。
Awk 恶作剧只是重新实现 IFS="=" read -r 的笨拙方式
@gniourf_gniourf “发布”文件与问题无关。我相信您没有查看 DELIMITER_VAL='=' 变量,对吧?无论如何,感谢您的贡献。 😊
@tripleee关于您对“awk”的评论,我会做一些测试来评估所有场景。无论如何,“awk”普遍存在于所有主要的 Linux 发行版中。所以我认为使用它没有任何问题。 😊
我不反对 Awk,但使用它笨拙地尝试替换 shell 内置插件并不是一个好主意。
P
Peter Mortensen

用于拆分由 ';' 分隔的字符串的单行进入一个数组是:

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 是新行)
请参阅 nickjb 的答案,以获取此想法的可行替代方案 stackoverflow.com/a/6583589/1032370
-1, 1. IFS 没有在那个子shell中设置(它被传递到“echo”的环境,这是一个内置的,所以无论如何都没有发生任何事情)。 2. $IN 被引用,因此不受 IFS 拆分的影响。 3.进程替换被空格分割,但这可能会破坏原始数据。
P
Petr Újezdský

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

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;;;。然后它会执行 for1 循环到 number-of-delimiters 的循环,按 ${#delims} 计数。最后一步是使用 cut 安全地获得第 $i 部分。