División de cadena con IFS


8

He escrito una secuencia de comandos de muestra para dividir la cadena pero no funciona como se esperaba

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

salida

One
XX
X
17.0.0
17 0 0

pero esperaba que la salida fuera:

      One
      XX
      X
      17.0.0
      17
      0
      0

Por cierto, si una de las respuestas a continuación resuelven su problema, por favor tome un momento y aceptarla haciendo clic en la marca de verificación a la izquierda. Eso marcará la pregunta como respondida y es la forma en que se expresan las gracias en los sitios de Stack Exchange.
terdon

Respuestas:


2

Solución, (ver también la respuesta de S. Chazelas para el fondo), con salida sensata:

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

Salida:

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

Notas:

  • Es mejor poner el segundo bucle condicional en el primer bucle.

  • bashsustitución de patrón ( "${i//.}") comprueba si hay un .en un elemento. (Una casedeclaración podría ser más simple, aunque menos similar al código del OP ).

  • reading $arraypor entrada <<< "${ADDR[3]}"es menos general que <<< "$i". Evita la necesidad de saber qué elemento tiene el .s.

  • El código supone que la impresión " Elemento: 17.0.0 " no es intencional. Si se pretende ese comportamiento , reemplace el bucle principal con:

    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 (*.*) ...sería una forma más canónica de verificar que $icontiene .(y también portátil para sh). Si te gustan los kshismos, mira también:[[ $i = *.* ]]
Stéphane Chazelas

@ StéphaneChazelas, ya mencionado caseen las notas al final, pero estamos de acuerdo. (Desde el OP utiliza tanto <<<y matrices , esto no es mucho de una shpregunta.)
agc

10

En versiones anteriores de bashusted tenía que citar variables después <<<. Eso fue arreglado en 4.4. En versiones anteriores, la variable se dividiría en IFS y las palabras resultantes se unirían en el espacio antes de almacenarse en el archivo temporal que constituye esa <<<redirección.

En 4.2 y anteriores, al redirigir las construcciones como reado command, esa división incluso tomaría el IFS para esa construcción (4.3 solucionó eso):

$ 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

Ese arreglado en 4.3:

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

Pero $atodavía está sujeto a la división de palabras allí:

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

En 4.4:

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

Para la portabilidad a versiones anteriores, indique su variable (o use de zshdónde <<<proviene en primer lugar y eso no tiene ese problema)

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

Tenga en cuenta que ese enfoque para dividir una cadena solo funciona para cadenas que no contienen caracteres de nueva línea. También tenga en cuenta que a..b.c.se dividiría en "a", "", "b", "c"(sin vaciar último elemento).

Para dividir cadenas arbitrarias, puede usar el operador split + glob en su lugar (lo que lo haría estándar y evitaría almacenar el contenido de una variable en un archivo temporal como lo <<<hace):

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

o:

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

El ''es preservar un elemento vacío final si lo hay. Eso también dividiría un elemento vacío $varen un elemento vacío.

O use un shell con un operador de división adecuado:

  • 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

Con awk te costaría una línea:

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 campo basado en múltiples caracteres, en nuestro caso -y.

La salida:

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

Lo mismo podría hacerse conIFS=-. read -r a array <<< "$IN"
Stéphane Chazelas

@ StéphaneChazelas, es diferente. Estás mostrando solo un paso para convertir una cadena en una matriz. Pero mi línea única está dedicada a cubrir todo: división en campos, procesamiento y salida. No estoy compitiendo con tu respuesta, son simplemente diferentes
RomanPerekhrest

0

Aquí mi camino:

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 se esperaba:

Num:17
Num:0
Num:0

Eso $INinvoca al operador split + glob. Aquí, no quieres la parte glob (prueba IN=*-*-/*-17.0.0por ejemplo), así que querrás hacerlo set -o noglobantes de invocarla. Vea mi respuesta para más detalles.
Stéphane Chazelas

1
En general, trate de evitar "guardar" IFSy configurarlo globalmente. Realmente solo desea cambiar el valor de IFSfor cuando $INse expande, y tampoco desea que se realice la expansión del nombre de ruta en la expansión. Además, OIFS=$IFSno distingue entre los casos en que IFSse estableció en una cadena vacía y cuando IFSse desarmó por completo.
chepner
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.