使用IFS分割字串


8

我已经编写了一个示例脚本来拆分字符串,但是无法正常工作

#!/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

顺便说一句,如果以下答案之一解决了您的问题,请花一点时间并单击左侧的复选标记以接受。这会将问题标记为已回答,并且是在Stack Exchange网站上表达感谢的方式。
terdon

Answers:


2

修复(请参阅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

输出:

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

笔记:

  • 这是更好地把有条件的第二循环第一个循环。

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

  • read荷兰国际集团$array通过输入<<< "${ADDR[3]}"是小于一般<<< "$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
    

1
case $i in (*.*) ...将是一种更规范的方法来检查$i包含的内容.(也可移植到sh)。如果你到kshisms,另见:[[ $i = *.* ]]
斯特凡Chazelas

@StéphaneChazelas,已经提到case注意事项在年底,但我们同意。(由于OP同时使用<<<数组,所以这不是什么大sh问题。)
AGC

10

在旧版本中,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

1

使用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

同样可以用做IFS=-. read -r a array <<< "$IN"
斯特凡Chazelas

@StéphaneChazelas,不一样。您仅展示了将字符串转换为数组的步骤。但是我的一行专心覆盖所有内容:拆分为字段,进行处理和输出。我无法与您的答案竞争,它们只是有所不同
RomanPerekhrest

0

这是我的方式:

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

$IN就是调用split + glob运算符。在这里,您不需要glob部分(IN=*-*-/*-17.0.0例如,尝试),因此您需要set -o noglob在调用它之前进行操作。请参阅我的答案以获取详细信息。
斯特凡Chazelas

1
通常,请尝试避免“保存” IFS并在全局范围内进行设置。您实际上只想更改IFSfor when $IN扩展的值,也不想在扩展上执行路径名扩展。此外,OIFS=$IFS在何时将IFS其设置为空字符串以及何时IFS完全未设置的情况下,不进行区分。
chepner
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.