Fractionner la chaîne à l'aide d'IFS

8

J'ai écrit un exemple de script pour diviser la chaîne, mais cela ne fonctionne pas comme prévu

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

production

One
XX
X
17.0.0
17 0 0

mais je m'attendais à ce que la sortie soit:

      One
      XX
      X
      17.0.0
      17
      0
      0
user112232
la source
Soit dit en passant, si l'une des réponses ci-dessous a résolu votre problème, veuillez prendre un moment et l' accepter en cliquant sur la coche à gauche. Cela marquera la réponse à la question et c'est ainsi que les remerciements sont exprimés sur les sites Stack Exchange.
terdon

Réponses:

2

Fix, (voir aussi la réponse de S. Chazelas pour le fond), avec une sortie sensible:

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

Production:

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

Remarques:

  • Il vaut mieux mettre la 2ème boucle conditionnelle dans la 1ère boucle.

  • bashsubstitution de motifs ( "${i//.}") vérifie s'il y a un .dans un élément. (Une casedéclaration pourrait être plus simple, quoique moins similaire au code OP .)

  • readen $arrayentrant <<< "${ADDR[3]}"est moins générale que <<< "$i". Cela évite d'avoir à savoir quel élément a le par ..

  • Le code suppose que l'impression de " Element: 17.0.0 " n'est pas intentionnelle. Si ce comportement est voulu, remplacez la boucle principale par:

    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
    
agc
la source
1
case $i in (*.*) ...serait un moyen plus canonique de vérifier que $icontient .(et aussi portable pour sh). Si vous aimez les kshismes, voir aussi:[[ $i = *.* ]]
Stéphane Chazelas
@ StéphaneChazelas, Déjà mentionné casedans les notes à la fin, mais nous sommes d'accord. (Puisque l'OP utilise à la fois les tableaux<<< et les tableaux , ce n'est pas vraiment une shquestion.)
agc
10

Dans les anciennes versions, bashvous deviez citer des variables après <<<. Cela a été corrigé dans 4.4. Dans les anciennes versions, la variable était divisée sur IFS et les mots résultants joints dans l'espace avant d'être stockés dans le fichier temporaire qui constitue cette <<<redirection.

En 4.2 et avant, lors de la redirection de buildins comme readou command, ce fractionnement prendrait même l'IFS pour ce builtin (4.3 corrigé cela):

$ 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

Celui-ci corrigé en 4.3:

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

Mais il $aest toujours sujet à la division des mots:

$ 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

Pour la portabilité vers les anciennes versions, citez votre variable (ou utilisez d' zshoù cela <<<vient en premier lieu et qui n'a pas ce problème)

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

Notez que cette approche pour diviser une chaîne ne fonctionne que pour les chaînes qui ne contiennent pas de caractères de nouvelle ligne. Notez également que a..b.c.serait divisé en "a", "", "b", "c"(pas vide dernier élément).

Pour diviser des chaînes arbitraires, vous pouvez utiliser l'opérateur split + glob à la place (ce qui le rendrait standard et éviterait de stocker le contenu d'une variable dans un fichier temporaire comme le <<<fait):

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

Il ''s'agit de conserver un élément vide de fin le cas échéant. Cela diviserait également un vide $varen un élément vide.

Ou utilisez un shell avec un opérateur de fractionnement approprié:

  • 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
Stéphane Chazelas
la source
1

Avec awk, cela vous coûterait une ligne:

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'[-.]'- séparateur de champs basé sur plusieurs caractères, dans notre cas -et.

Le résultat:

Element : One
Element : XX
Element : X
Num : 17
Num : 0
Num : 0
RomanPerekhrest
la source
La même chose pourrait être faite avecIFS=-. read -r a array <<< "$IN"
Stéphane Chazelas
@ StéphaneChazelas, c'est différent. Vous montrez juste l'étape de conversion d'une chaîne en tableau. Mais mon une ligne est dédiée à couvrir tout: la division en champs, le traitement et la sortie. Je ne suis pas en concurrence avec votre réponse, ils sont juste différents
RomanPerekhrest
0

Voici mon chemin:

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

résultat comme prévu:

Num:17
Num:0
Num:0
tonioc
la source
Cela $INappelle l'opérateur split + glob. Ici, vous ne voulez pas la partie glob (essayez IN=*-*-/*-17.0.0par exemple), donc vous devriez le faire set -o noglobavant de l'invoquer. Voir ma réponse pour plus de détails.
Stéphane Chazelas
1
En général, essayez d'éviter de «sauvegarder» IFSet de le définir globalement. Vous ne voulez vraiment changer que la valeur de IFSquand $INest développé, et vous ne voulez pas non plus que l'expansion du nom de chemin soit effectuée sur l'expansion. En outre, OIFS=$IFSne fait pas de distinction entre les cas où a IFSété défini sur une chaîne vide et lorsqu'il IFSn'a pas été complètement défini.
chepner