Dividir cadeia usando IFS


8

Eu escrevi um exemplo de script para dividir a string, mas não está funcionando conforme o 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

resultado

One
XX
X
17.0.0
17 0 0

mas eu esperava que a saída fosse:

      One
      XX
      X
      17.0.0
      17
      0
      0

A propósito, se uma das respostas abaixo resolver seu problema, reserve um momento e aceite-o clicando na marca de seleção à esquerda. Isso marcará a pergunta como respondida e é assim que os agradecimentos são expressos nos sites do Stack Exchange.
terdon

Respostas:


2

Correção (veja também a resposta de S. Chazelas para obter informações detalhadas ), com saída sensível:

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

Resultado:

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

Notas:

  • É melhor colocar o condicional ciclo no ciclo.

  • bashsubstituição de padrão ( "${i//.}") verifica se existe .um elemento. (Uma casedeclaração pode ser mais simples, embora menos semelhante ao código do OP .)

  • reading $arrayintroduzindo <<< "${ADDR[3]}"é menos do que geral <<< "$i". Evita a necessidade de saber qual elemento possui os .s.

  • O código pressupõe que a impressão " Elemento: 17.0.0 " não é intencional. Se esse comportamento for intencional, 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

1
case $i in (*.*) ...seria uma maneira mais canônica de verificar se $icontém .(e também é portátil para sh). Se você gosta de kismo, veja também:[[ $i = *.* ]]
Stéphane Chazelas 10/10

@ StéphaneChazelas, Já mencionado casenas notas no final, mas concordamos. (Uma vez que o OP usa tanto <<<e matrizes , isso não é muito de uma shquestão.)
AGC

10

Nas versões antigas, bashvocê tinha que citar variáveis ​​depois <<<. Isso foi corrigido no 4.4. Nas versões anteriores, 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.

No 4.2 e antes, ao redirecionar os componentes internos como readou command, essa divisão levaria o IFS para o componente interno (4.3 corrigiu isso):

$ 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

Essa foi corrigida em 4.3:

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

Mas $aainda está sujeito à divisão de palavras por lá:

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

No 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 de zshonde ela <<<vem em primeiro lugar e que não possui 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 funciona apenas para strings que não contêm caracteres de nova linha. Observe também que a..b.c.seria dividida em "a", "", "b", "c"(sem esvaziar último elemento).

Para dividir seqüências arbitrárias, você pode usar o operador split + glob (o que a tornaria padrão e evitaria armazenar o conteúdo de uma variável em um arquivo temporário da mesma <<<forma):

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 ''objetivo é preservar um elemento vazio à direita, se houver. Isso também dividiria um vazio $varem 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

1

Com o awk , 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 com base em vários caracteres, no nosso caso -e.

A saída:

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

O mesmo pode ser feito com #IFS=-. read -r a array <<< "$IN"
Stéphane Chazelas 10/10

@ StéphaneChazelas, é diferente. Você está mostrando apenas um passo na conversão de uma string em array. Mas minha linha única é dedicada a cobrir tudo: dividir em campos, processar e produzir. Eu não estou competir com a sua resposta, eles são apenas diferentes
RomanPerekhrest

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 conforme o esperado:

Num:17
Num:0
Num:0

Isso $INestá chamando o operador split + glob. Aqui, você não deseja a parte da glob (tente IN=*-*-/*-17.0.0por exemplo), por isso set -o noglobantes de invocá-la. Veja minha resposta para detalhes.
Stéphane Chazelas 10/10

1
Em geral, tente evitar "salvar" IFSe configurá-lo globalmente. Você realmente deseja alterar apenas o valor de IFSquando $INé expandido e também não deseja que a expansão do nome do caminho seja executada na expansão. Além disso, OIFS=$IFSnão faz distinção entre os casos quando IFSfoi definido como uma sequência vazia e quando IFSestava completamente desmarcado.
chepner
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.