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

问:

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

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

现在我想用 ; 分隔符分割字符串,这样我就有:

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

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

根据以下答案的建议,我最终得到了以下结果:

#!/usr/bin/env bash

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

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

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

输出:

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

有一个解决方案涉及将 Internal_field_separator (IFS) 设置为 ;。我不确定该答案发生了什么,您如何将 IFS 重置为默认值?

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

答1:

打造属于自己的副业,开启自由职业之旅,从huntsbot.com开始!

您可以设置 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 中修复。

答2:

huntsbot.com提供全网独家一站式外包任务、远程工作、创意产品分享与订阅服务!

取自 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=KaTeX parse error: Undefined control sequence: \n at position 2: '\̲n̲'; arrIN=(IN);取消设置 IFS;

我只想补充:这是最简单的,您可以使用 ${arrIN[1]} 访问数组元素(当然从零开始)

找到它:在 ${} 中修改变量的技术被称为“参数扩展”。

不,我认为当也存在空格时这不起作用......它将“,”转换为“”,然后构建一个以空格分隔的数组。

非常简洁,但有一般用途的注意事项:shell 将分词 和扩展 应用于字符串,这可能是不受欢迎的;试试吧。 IN="bla@some.com;john@home.com;*;broken apart"。简而言之:如果您的标记包含嵌入的空格和/或字符,这种方法将失效。例如 * 恰好使令牌匹配当前文件夹中的文件名。

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

答3:

huntsbot.com洞察每一个产品背后的需求与收益,从而捕获灵感

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

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 ?

答4:

huntsbot.com洞察每一个产品背后的需求与收益,从而捕获灵感

我已经看到几个引用 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 也非常有用,即

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

答5:

一个优秀的自由职业者,应该有对需求敏感和精准需求捕获的能力,而huntsbot.com提供了这个机会

兼容的答案

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

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

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

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

请求的字符串

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

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

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

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

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

在 pure bash 中,我们可以创建一个 array,其中的元素由 IFS 的临时值(输入字段分隔符)。除其他外,IFS 告诉 bash 在定义数组时应将哪些字符视为元素之间的分隔符:

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

# 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 ")
# IN='bla@some.com;john@home.com;Full Name '

(我们也可以使用 declare -p 显示这些变量的内容:)

declare -p IN fields
# declare -- IN="bla@some.com;john@home.com;Full Name "
# declare -a fields=([0]="bla@some.com" [1]="john@home.com" [2]="Full Name ")

请注意,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 ]

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

while [ "$fields" ] ;do
    echo "> [$fields]"
    # slice the array 
    fields=("${fields[@]:1}")
    done
# > [bla@some.com]
# > [john@home.com]
# > [Full Name ]

如果你只想要一个简单的数组打印输出,你甚至不需要循环它:

printf "> [%s]\n" "${fields[@]}"
# > [bla@some.com]
# > [john@home.com]
# > [Full Name ]

更新:最近的 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" \0 是无用的。)

 

```java
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 ', 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" ', done.


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

如果您不能使用 bash,或者如果您想编写可以在许多不同的 shell 中使用的东西,您通常不能使用 bashisms – 并且这包括我们在上述解决方案中一直使用的数组。

但是,我们不需要使用数组来循环字符串的“元素”。在许多 shell 中都有一种语法用于从模式的 first 或 last 出现中删除字符串的子字符串。请注意,* 是代表零个或多个字符的通配符:

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

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

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

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

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

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

IN="bla@some.com;john@home.com;Full Name "
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 ]

玩得开心!

#、##、% 和 %% 替换具有 IMO 更容易记住的解释(它们删除了多少):# 和 % 删除可能的最短匹配字符串,并且## 和 %% 删除尽可能长的时间。

IFS=\; read -a fields <<<"$var" 在换行符上失败并添加尾随换行符。另一个解决方案删除了一个尾随的空字段。

这个答案非常史诗。

如果您将可移植 shell 答案的 while 条件更改为 [ "$IN" != "$iter" ],您将不需要最后的条件,只需要它的 else 子句。整个循环可以压缩为两条内线:while [ "$IN" != "$iter" ]; do iter="${IN%%;*}" IN="${IN#*;}"; echo "> [$iter]"; done

@AdamKatz 非常聪明,答案已编辑,谢谢!

答6:

huntsbot.com – 高效赚钱,自由工作

这对我有用:

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:基本上总是如此,至少在您了解何时可以甚至应该省略引号之前)。

答7:

HuntsBot周刊–不定时分享成功产品案例,学习他们如何成功建立自己的副业–huntsbot.com

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

答8:

huntsbot.com提供全网独家一站式外包任务、远程工作、创意产品分享与订阅服务!

这种方法怎么样:

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 '。然后 echo "${Array[2]}" 将打印一个带有换行符的字符串。在这种情况下,set -- "$IN" 也是必需的。是的,为防止全局扩展,解决方案应包括 set -f。

答9:

huntsbot.com汇聚了国内外优秀的初创产品创意,可按收入、分类等筛选,希望这些产品与实践经验能给您带来灵感。

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 好点。但是,当我写下那个答案时,数组分配不在最初的问题中。

答10:

huntsbot.com汇聚了国内外优秀的初创产品创意,可按收入、分类等筛选,希望这些产品与实践经验能给您带来灵感。

这也有效:

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

答11:

huntsbot.com聚合了超过10+全球外包任务平台的外包需求,寻找外包任务与机会变的简单与高效。

对 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 中使用不带引号的变量扩展来扩展通配符 *。

我真的很喜欢这个解决方案。对其工作原理的描述将非常有用,并使其成为更好的整体答案。

原文链接:https://www.huntsbot.com/qa/ve9Y/how-do-i-split-a-string-on-a-delimiter-in-bash?lang=zh_CN&from=csdn

一个优秀的自由职业者,应该有对需求敏感和精准需求捕获的能力,而huntsbot.com提供了这个机会

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值