使用 IFS 分割字符串

使用 IFS 分割字符串

我已经编写了一个示例脚本来分割字符串,但它没有按预期工作

#!/bin/bash
IN="One-XX-X-17.0.0"
IFS='-' read -r -a ADDR <<< "$IN"
for i in "${ADDR[@]}"; do
    echo "Element:$i"
done
#split 17.0.0 into NUM
IFS='.' read -a array <<<${ADDR[3]};
for element in "${array[@]}"
do
    echo "Num:$element"
done
  • 实际产量
    One
    XX
    X
    17.0.0
    17 0 0
    
  • 但我预计输出是:
    One
    XX
    X
    17.0.0
    17
    0
    0
    

答案1

在旧版本中,bash您必须在 后引用变量<<<。这个问题在 4.4 中得到了修复。在旧版本中,变量将在 IFS 上拆分,并在存储在构成该<<<重定向的临时文件中之前在空间上连接生成的单词。

在 4.2 及之前的版本中,当重定向诸如read或 之类的内置函数时command,该拆分甚至会采用该内置函数的 IFS(4.3 修复了该问题):

$ bash-4.2 -c 'a=a.b.c.d; IFS=. read x <<< $a; echo  "$x"'
a b c d
$ bash-4.2 -c 'a=a.b.c.d; IFS=. cat <<< $a'
a.b.c.d
$ bash-4.2 -c 'a=a.b.c.d; IFS=. command cat <<< $a'
a b c d

4.3 中修复了这个问题:

$ bash-4.3 -c 'a=a.b.c.d; IFS=. read x <<< $a; echo  "$x"'
a.b.c.d

$a仍然受到分词的影响:

$ bash-4.3 -c 'a=a.b.c.d; IFS=.; read x <<< $a; echo  "$x"'
a b c d

在 4.4 中:

$ bash-4.4 -c 'a=a.b.c.d; IFS=.; read x <<< $a; echo  "$x"'
a.b.c.d

为了移植到旧版本,请引用您的变量(或首先使用zsh该变量的来源并且不存在该问题)<<<

$ bash-any-version -c 'a=a.b.c.d; IFS=.; read x <<< "$a"; echo "$x"'
a.b.c.d

请注意,这种分割字符串的方法仅适用于不包含换行符的字符串。另请注意,它将a..b.c.被拆分为"a", "", "b", "c"(最后一个元素没有空)。

要分割任意字符串,您可以使用 split+glob 运算符(这将使其成为标准并避免像以前那样将变量的内容存储在临时文件中<<<):

var='a.new
line..b.c.'
set -o noglob # disable glob
IFS=.
set -- $var'' # split+glob
for i do
  printf 'item: <%s>\n' "$i"
done

或者:

array=($var'') # in shells with array support

''是为了保留尾随的空元素(如果有)。这也会将一个空元素拆分$var为一个空元素。

或者使用带有适当拆分运算符的 shell:

  • zsh

    array=(${(s:.:)var} # removes empty elements
    array=("${(@s:.:)var}") # preserves empty elements
    
  • rc

    array = ``(.){printf %s $var} # removes empty elements
    
  • fish

    set array (string split . -- $var) # not for multiline $var
    

答案2

修复,(另请参阅S.查泽拉斯的回答用于背景),具有合理的输出:

#!/bin/bash
IN="One-XX-X-17.0.0"
IFS='-' read -r -a ADDR <<< "$IN"
for i in "${ADDR[@]}"; do
    if [ "$i" = "${i//.}" ] ; then 
        echo "Element:$i" 
        continue
    fi
    # split 17.0.0 into NUM
    IFS='.' read -a array <<< "$i"
    for element in "${array[@]}" ; do
        echo "Num:$element"
    done
done

输出:

Element:One
Element:XX
Element:X
Num:17
Num:0
Num:0

笔记:

  • 最好加上条件第二名环形第一名环形。

  • bash模式替换 ( ) 检查元素中"${i//.}"是否有 a 。 .case声明可能更简单,尽管与OP的代码。)

  • read$array通过输入进行ing<<< "${ADDR[3]}"不如 ing 通用<<< "$i"。它避免了需要知道哪个元素有.s。

  • 该代码假设打印“元素:17.0.0“是无意的。如果那种行为有意将主循环替换为:

    for i in "${ADDR[@]}"; do
       echo "Element:$i" 
       if [ "$i" != "${i//.}" ] ; then 
       # split 17.0.0 into NUM
           IFS='.' read -a array <<< "$i"
           for element in "${array[@]}" ; do
               echo "Num:$element"
           done
       fi
    done
    

答案3

awk这将花费你一行:

IN="One-XX-X-17.0.0"

awk -F'[-.]' '{ for(i=1;i<=NF;i++) printf "%s : %s\n",($i~/^[0-9]+$/?"Num":"Element"),$i }' <<<"$IN"
  • -F'[-.]'-- 在我们的例子中基于多个字符的字段分隔符.

输出:

Element : One
Element : XX
Element : X
Num : 17
Num : 0
Num : 0

答案4

这是我的方式:

OIFS=$IFS
IFS='-'
IN="One-XX-X-17.0.0"
ADDR=($IN)
for i in "${ADDR[@]}"; do
 echo "Element:$i"
done
IFS='.'
array=(${ADDR[3]})
for element in "${array[@]}"
do
  echo "Num:$element"
done

结果符合预期:

Num:17
Num:0
Num:0

相关内容