Dividi stringa usando IFS


8

Ho scritto uno script di esempio per dividere la stringa ma non funziona come previsto

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

produzione

One
XX
X
17.0.0
17 0 0

ma mi aspettavo che l'output fosse:

      One
      XX
      X
      17.0.0
      17
      0
      0

A proposito, se una delle risposte di seguito ha risolto il problema, ti preghiamo di prendere un momento e accettarlo facendo clic sul segno di spunta a sinistra. Questo segnerà la domanda come risposta ed è il modo in cui i ringraziamenti sono espressi sui siti di Stack Exchange.
terdon

Risposte:


2

Fix, (vedi anche la risposta di S. Chazelas per lo sfondo), con output sensato:

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

Produzione:

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

Appunti:

  • E 'meglio mettere il condizionale secondo ciclo nel il 1 ° ciclo.

  • bashpattern substitution ( "${i//.}") controlla se c'è .un elemento in. ( caseUn'affermazione potrebbe essere più semplice, sebbene meno simile al codice del PO .)

  • reading $arrayinserendo <<< "${ADDR[3]}"è meno generale di <<< "$i". Evita la necessità di sapere quale elemento ha la .s.

  • Il codice presuppone che la stampa di " Elemento: 17.0.0 " non sia intenzionale. Se si intende tale comportamento , sostituire il loop principale 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 (*.*) ...sarebbe un modo più canonico per verificare che $icontenga .(e sia anche portatile sh). Se ti piacciono i kshismi, vedi anche:[[ $i = *.* ]]
Stéphane Chazelas,

@ StéphaneChazelas, già menzionato casenelle note alla fine, ma siamo d'accordo. (Dato che l'OP utilizza sia gli array<<< che gli array , questa non è una gran shdomanda.)
agc

10

Nelle vecchie versioni bashhai dovuto citare le variabili dopo <<<. Ciò è stato risolto in 4.4. Nelle versioni precedenti, la variabile veniva suddivisa su IFS e le parole risultanti venivano unite nello spazio prima di essere archiviate nel file temporaneo che costituisce il <<<reindirizzamento.

In 4.2 e prima, quando si reindirizzava builtin come reado command, tale suddivisione avrebbe persino preso l'IFS per quel builtin (4.3 risolto che):

$ 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

Quello risolto in 4.3:

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

Ma $aè ancora soggetto alla suddivisione delle parole lì:

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

In 4.4:

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

Per la portabilità alle versioni precedenti, cita la tua variabile (o usa da zshdove <<<proviene in primo luogo e che non ha quel problema)

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

Si noti che tale approccio alla divisione di una stringa funziona solo per le stringhe che non contengono caratteri di nuova riga. Si noti inoltre che a..b.c.sarebbe diviso in "a", "", "b", "c"(senza svuotare ultimo elemento).

Per dividere stringhe arbitrarie puoi usare invece l'operatore split + glob (che lo renderebbe standard ed eviterebbe di archiviare il contenuto di una variabile in un file temporaneo <<<):

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

È ''preservare un elemento vuoto finale se presente. Ciò dividerebbe anche un vuoto $varin un elemento vuoto.

O utilizzare una shell con un operatore di divisione appropriato:

  • 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 ti costerebbe una riga:

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'[-.]'- separatore di campo basato su più caratteri, nel nostro caso -e.

Il risultato:

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

Lo stesso potrebbe essere fatto conIFS=-. read -r a array <<< "$IN"
Stéphane Chazelas

@ StéphaneChazelas, è diverso. Stai mostrando solo un passo con la conversione di una stringa in array. Ma la mia linea singola è dedicata a coprire tutto: suddivisione in campi, elaborazione e produzione. Non sono in concorrenza con la tua risposta, sono solo diversi
RomanPerekhrest,

0

Ecco la mia strada:

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

risultato come previsto:

Num:17
Num:0
Num:0

Quello $INsta invocando l'operatore split + glob. Qui, non vuoi la parte glob (prova IN=*-*-/*-17.0.0ad esempio), quindi ti consigliamo di fare set -o noglobprima di invocarla. Vedi la mia risposta per i dettagli.
Stéphane Chazelas,

1
In generale, cerca di evitare il "salvataggio" IFSe impostarlo a livello globale. Vuoi davvero cambiare il valore di IFSfor quando $INviene espanso e non vuoi che l'espansione del nome percorso venga eseguita sull'espansione. Inoltre, OIFS=$IFSnon distingue tra i casi in cui IFSera impostato su una stringa vuota e quando IFSera completamente non impostato.
Chepner,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.