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

这篇博客讨论了如何在Bash shell中使用不同方法按分隔符拆分字符串。内容涵盖了IFS(Internal Field Separator)的设置、数组的使用,以及各种解决方案,包括兼容不同Bash版本的技巧。文章提供了多个示例,展示了如何将字符串拆分成数组,特别提到了bash 4.4及以上版本的新特性。
摘要由CSDN通过智能技术生成

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

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括弧,它可以工作。


#1楼

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

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

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


#2楼

这也适用:

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。


#3楼

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

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解决方案。


#4楼

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

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

会给

bla@some.com john@home.com

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


#5楼

兼容答案

对于这个SO问题,在已经有很多不同的方法可以做到这一点。 但是bash具有许多特殊功能,即所谓的bashism可以很好地工作,但是不能在任何其他

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

在我的Debian GNU / Linux上 ,有一个称为标准外壳,但是我知道很多人喜欢使用

最后,在很小的情况下,有一个名为的特殊工具,带有他自己的外壳解释器( )。

要求的字串

SO问题中的字符串示例是:

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

由于这可能对空格有用,并且因为空格可以修改例程的结果,所以我更喜欢使用以下示例字符串:

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

根据定界符分割字符串(版本> = 4.2)

bash下,我们可以使用数组IFS

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

 oIFS="$IFS" IFS=";" declare -a fields=($var) IFS="$oIFS" unset oIFS 

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

在最近的bash下使用此语法不会为当前会话更改$IFS ,而仅会为当前命令更改:

set | grep ^IFS=
IFS=$' \t\n'

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

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请求变量内容:

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是进行拆分的最快方法,因为没有分叉 ,也没有调用任何外部资源。

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

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

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

while [ "$fields" ] ;do
    echo "> [$fields]"
    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

你可以玩mapfile

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

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

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

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

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

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.

或者在函数中通过<<< bash语法添加的Drop newline:

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

将呈现相同的输出:

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下使用的东西,则不必使用bashisms

在许多Shell中都有一种语法,用于在子字符串的第一次最后一次出现时将字符串拆分:

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

(缺少此内容是我的答案发布的主要原因;)

Score_Under指出:

#%删除最短的匹配字符串,然后

##%%删除最长的时间。

其中###表示字符串的左边 (开始)开始,以及

%%%表示字符串的右边 (结尾)开始。

这个小示例脚本在下运行良好,并且也在Mac-OS的bash下进行了测试:

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

玩得开心!


#6楼

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

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

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

#7楼

使用内置的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

#8楼

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


#9楼

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

对于这项工作,关键是要使用-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的更新

由于击4.4,内建的mapfile (又名readarray )支持-d选项来指定的分隔符。 因此,另一种规范的方式是:

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

#10楼

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


#11楼

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

#12楼

在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

//表示全局替换。


#13楼

我看到了几个参考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这个文件,并选择用于进一步处理的特定领域。


#14楼

这是干净的三层纸:

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

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

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


#15楼

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

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

#16楼

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

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} 。 最后一步是使用cut安全地获得第$i个零件。


#17楼

大家好!

这是我的答案!

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. 您将不会有空格的问题 。 该值将在数组中正确分隔!

[]的


#18楼

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

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

#19楼

无需设置IFS

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

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

你会得到:

b = foo
c = bar

#20楼

这对我有用:

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

#21楼

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


#22楼

以下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 "$@"

#23楼

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

#24楼

取自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;

#25楼

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

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

#26楼

这是最简单的方法。

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

#27楼

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

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

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


#28楼

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

#29楼

您可以设置内部字段分隔符 (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"

#30楼

这种方法怎么样:

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

资源

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值