Dividir string usando o IFS

7

Eu escrevi um script de exemplo para dividir a string, mas ela não está funcionando como esperado

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

saída

One
XX
X
17.0.0
17 0 0

mas esperava que a saída fosse:

      One
      XX
      X
      17.0.0
      17
      0
      0
    
por user112232 10.10.2017 / 12:26

4 respostas

2

Corrigir, (veja também resposta de S. Chazelas para o fundo), com resultados sensatos:

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

Saída:

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

Notas:

  • É melhor colocar o laço segundo condicional no primeiro loop.

  • bash padrão de substituição ( "${i//.}" ) verifica se há . em um elemento. (Uma instrução case pode ser mais simples, embora menos semelhante ao código do OP ).

  • read ing $array inserindo <<< "${ADDR[3]}" é menos geral que <<< "$i" . Evita a necessidade de saber qual elemento tem o . s.

  • O código pressupõe que a impressão " Elemento: 17.0.0 " não é intencional. Se esse comportamento for pretendido, substitua o loop principal por:

    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
    
por 10.10.2017 / 13:05
8

Nas versões antigas de bash você tinha que citar as variáveis após <<< . Isso foi corrigido em 4.4. Em versões mais antigas, a variável seria dividida no IFS e as palavras resultantes unidas no espaço antes de serem armazenadas no arquivo temporário que compõe esse redirecionamento <<< .

Em 4.2 e antes, ao redirecionar recursos internos como read ou command , essa divisão levaria o IFS para aquele construído (4.3 corrigido):

$ 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

Aquele fixado em 4.3:

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

Mas $a ainda está sujeito à divisão de palavras:

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

Em 4.4:

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

Para portabilidade para versões mais antigas, cite sua variável (ou use zsh de onde vem <<< e que não tem esse problema)

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

Observe que essa abordagem para dividir uma string só funciona para strings que não contêm caracteres de nova linha. Observe também que a..b.c. seria dividido em "a" , "" , "b" , "c" (sem último elemento vazio).

Para dividir as strings arbitrárias, você pode usar o operador split + glob (o que tornaria padrão e evitar o armazenamento do conteúdo de uma variável em um arquivo temporário como <<< ):

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

ou:

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

O '' é para preservar um elemento vazio final, se houver. Isso também dividiria um $var vazio em um elemento vazio.

Ou use um shell com um operador de divisão adequado:

  • 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
    
por 10.10.2017 / 12:43
1

Com o awk você custaria uma linha:

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'[-.]' - separador de campos baseado em vários caracteres, no nosso caso - e .

A saída:

Element : One
Element : XX
Element : X
Num : 17
Num : 0
Num : 0
    
por 10.10.2017 / 12:36
0

Aqui do meu jeito:

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

resultado como esperado:

Num:17
Num:0
Num:0
    
por 10.10.2017 / 12:58