bash - split函数 - shell按空格分割字符串




如何在Bash中的分隔符上分割字符串? (20)

我有这个字符串存储在一个变量中:

IN="[email protected];[email protected]"

现在我想分割字符串; 分隔符,以便我有:

ADDR1="[email protected]"
ADDR2="[email protected]"

我不一定需要ADDR1ADDR2变量。 如果它们是更好的数组的元素。

根据下面的答案提出建议后,我结束了以下的工作:

#!/usr/bin/env bash

IN="[email protected];[email protected]"

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

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

输出:

> [[email protected]]
> [[email protected]]

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

RE: IFS解决方案,我试过了,它工作,我保留旧的IFS ,然后恢复它:

IN="[email protected];[email protected]"

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

IFS=$OIFS

顺便说一句,当我尝试

mails2=($IN)

在循环打印时,我只有第一个字符串,没有$IN左右括号。


兼容答案

对于这个问题,在bash已经有很多不同的方法来做到这一点。 但bash有很多特殊功能,所谓的bashism良好,但在其他shell不起作用。

特别是, 数组关联数组模式替换都是纯粹的bashisms,并且可能无法在其他shell中工作。

在我的Debian GNU / Linux上 ,有一个叫做dash的标准 shell,但我知道很多人喜欢使用ksh 。

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

请求的字符串

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

IN="[email protected];[email protected]"

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

 IN="[email protected];[email protected];Full Name <[email protected]>"

根据bash分隔符分割字符串(version> = 4.2)

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

var="[email protected];[email protected];Full Name <[email protected]>"

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

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

在最近的bash下使用这种语法不会改变当前会话的$IFS ,但仅限于当前命令:

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

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

set | grep ^fields=\\\|^var=
fields=([0]="[email protected]" [1]="[email protected]" [2]="Full Name <[email protected]>")
var='[email protected];[email protected];Full Name <[email protected]>'

我们可以使用declare -p来请求可变内容:

declare -p var fields
declare -- var="[email protected];[email protected];Full Name <[email protected]>"
declare -a fields=([0]="[email protected]" [1]="[email protected]" [2]="Full Name <[email protected]>")

read是进行拆分的最快方式,因为没有叉子 ,也没有外部资源调用。

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

for x in "${fields[@]}";do
    echo "> [$x]"
    done
> [[email protected]]
> [[email protected]]
> [Full Name <[email protected]>]

或者在处理之后丢弃每个字段(我喜欢这种转换方法):

while [ "$fields" ] ;do
    echo "> [$fields]"
    fields=("${fields[@]:1}")
    done
> [[email protected]]
> [[email protected]]
> [Full Name <fulnam[email protected]>]

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

printf "> [%s]\n" "${fields[@]}"
> [[email protected]]
> [[email protected]]
> [Full Name <[email protected]>]

根据shell分隔符分割字符串

但是如果你会写很多可用的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指出的Score_Under :

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

##%%删除最长的可能。

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

var="[email protected];[email protected];Full Name <[email protected]>"
while [ "$var" ] ;do
    iter=${var%%;*}
    echo "> [$iter]"
    [ "$var" = "$iter" ] && \
        var='' || \
        var="${var#*;}"
  done
> [[email protected]]
> [[email protected]]
> [Full Name <[email protected]>]

玩的开心!


Maybe not the most elegant solution, but works with * and spaces:

IN="[email protected] me.com;*;[email protected]"
for i in `delims=${IN//[^;]}; seq 1 $((${#delims} + 1))`
do
   echo "> [`echo $IN | cut -d';' -f$i`]"
done

输出

> [[email protected] me.com]
> [*]
> [[email protected]]

Other example (delimiters at beginning and end):

IN=";[email protected] me.com;*;[email protected];"
> []
> [[email protected] me.com]
> [*]
> [[email protected]]
> []

Basically it removes every character other than ; making delims eg. ;;; 。 Then it does for loop from 1 to number-of-delimiters as counted by ${#delims} . The final step is to safely get the $i th part using cut .



下面的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 "[email protected]"


在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

//表示全局替换。


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

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

你可以使用这种循环来初始化一个数组,但是可能有一种更简单的方法来完成它。 但希望这有助于。


如果你不使用数组,那么这个班轮怎么样?

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


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


有一个简单而智能的方式是这样的:

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

用单引号分隔用';'分隔的字符串 成一个数组是:

IN="[email protected];[email protected]"
ADDRS=( $(IFS=";" echo "$IN") )
echo ${ADDRS[0]}
echo ${ADDRS[1]}

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


这也适用于:

IN="[email protected];[email protected]"
echo ADD1=`echo $IN | cut -d \; -f 1`
echo ADD2=`echo $IN | cut -d \; -f 2`

小心,这个解决方案并不总是正确的。 如果您仅通过“[email protected]”,它会将其分配给ADD1和ADD2。


这是最简单的方法。

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


这里有一些很酷的答案(特别是errator),但是对于类似于其他语言中的分裂的东西 - 这就是我原来的问题所表达的意思 - 我在这个问题上解决了问题:

IN="[email protected];[email protected]"
declare -a a="(${IN/;/ })";

现在${a[0]}${a[1]}等等,就像你期望的那样。 使用${#a[*]}作为条款数量。 当然也可以迭代:

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

重要的提示:

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


采用Bash shell脚本分割数组

IN="[email protected];[email protected]"
arrIN=(${IN//;/ })

说明:

这种构造取代了所有出现的';' (最初的//表示全局替换)与字符串IN (单个空格),然后将空格分隔的字符串解释为一个数组(这是括号括起来的)。

花括号内用来替换每个';'的语法 带有' '字符的字符称为参数扩展

有一些常见的陷阱:

  1. 如果原始字符串有空格,则需要使用IFS
    • IFS=':'; arrIN=($IN); unset IFS;
  2. 如果原始字符串中包含空格分隔符是新行,则可以使用以下内容设置IFS
    • IFS=$'\n'; arrIN=($IN); unset IFS;


IN='[email protected];[email protected];Charlie Brown <[email protected];!"#$%&/()[]{}*? 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

输出:

[email protected]
[email protected]
Charlie Brown <[email protected]
!"#$%&/()[]{}*? are no problem
simple is beautiful :-)

说明:使用括号()进行简单赋值,可以将分号分隔的列表转换为数组,前提是您拥有正确的IFS。 标准FOR循环像往常一样处理该数组中的单个项目。 请注意,给IN变量的列表必须是“硬”引用的,也就是说,只有一个刻度。

IFS必须被保存并且被恢复,因为Bash并不像命令那样对待一个赋值。 另一种解决方法是将分配包装在一个函数中,并使用修改的IFS调用该函数。 在这种情况下,不需要单独保存/恢复IFS。 感谢“Bize”的指出。







scripting