使用 IFS 拆分字符串

use*_*232 11 bash shell-script lfs

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

#!/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
Run Code Online (Sandbox Code Playgroud)

输出

One
XX
X
17.0.0
17 0 0
Run Code Online (Sandbox Code Playgroud)

但我预计输出是:

      One
      XX
      X
      17.0.0
      17
      0
      0
Run Code Online (Sandbox Code Playgroud)

Sté*_*las 11

在旧版本中,bash您必须在<<<. 这在 4.4 中已修复。在旧版本中,变量将在 IFS 上拆分,结果单词在空间上连接,然后存储在构成该<<<重定向的临时文件中。

在 4.2 及之前,当重定向类似reador 的内置函数时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
Run Code Online (Sandbox Code Playgroud)

4.3 中修复的那个:

$ bash-4.3 -c 'a=a.b.c.d; IFS=. read x <<< $a; echo  "$x"'
a.b.c.d
Run Code Online (Sandbox Code Playgroud)

$a仍然会在那里进行分词:

$ bash-4.3 -c 'a=a.b.c.d; IFS=.; read x <<< $a; echo  "$x"'
a b c d
Run Code Online (Sandbox Code Playgroud)

在 4.4:

$ bash-4.4 -c 'a=a.b.c.d; IFS=.; read x <<< $a; echo  "$x"'
a.b.c.d
Run Code Online (Sandbox Code Playgroud)

为了可移植到旧版本,引用您的变量(或首先使用zsh<<<来自哪里并且没有那个问题)

$ bash-any-version -c 'a=a.b.c.d; IFS=.; read x <<< "$a"; echo "$x"'
a.b.c.d
Run Code Online (Sandbox Code Playgroud)

请注意,这种拆分字符串的方法仅适用于不包含换行符的字符串。另请注意,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
Run Code Online (Sandbox Code Playgroud)

或者:

array=($var'') # in shells with array support
Run Code Online (Sandbox Code Playgroud)

''是维护如有尾随空元素。这也会将$var一个空元素拆分为一个空元素。

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


agc*_*agc 4

修复(另请参阅S. Chazelas 的背景答案),并提供合理的输出:

#!/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
Run Code Online (Sandbox Code Playgroud)

输出:

Element:One
Element:XX
Element:X
Num:17
Num:0
Num:0
Run Code Online (Sandbox Code Playgroud)

笔记:

  • 最好将条件第二个循环放在第一个循环

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

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

  • 该代码假定打印“ Element: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
    
    Run Code Online (Sandbox Code Playgroud)