Passa attraverso una variabile di shell separata da virgole


109

Supponiamo di avere una variabile di shell Unix come di seguito

variable=abc,def,ghij

Voglio estrarre tutti i valori ( abc, defe ghij) usando un ciclo for e passare ogni valore in una procedura.

Lo script dovrebbe consentire l'estrazione di un numero arbitrario di valori separati da virgole da $variable.


Cosa vuoi fare esattamente ripetendo?
SMA

1
Voglio passare ogni campo (diciamo abc) come argomento a una procedura.
Ramanathan K

Risposte:


125

Puoi utilizzare il seguente script per attraversare dinamicamente la tua variabile, indipendentemente dal numero di campi che ha purché sia ​​separata solo da virgole.

variable=abc,def,ghij
for i in $(echo $variable | sed "s/,/ /g")
do
    # call your procedure/other scripts here below
    echo "$i"
done

Invece della echo "$i"chiamata sopra, tra il doe doneall'interno del ciclo for, puoi invocare la tua procedura proc "$i".


Aggiornamento : lo snippet precedente funziona se il valore della variabile non contiene spazi. Se si dispone di tale requisito, utilizzare una delle soluzioni che possono cambiare IFSe quindi analizzare la variabile.


Spero che questo ti aiuti.


8
Questo non funziona se $variablecontiene spazi bianchi, ad esempio variable=a b,c,d=> stampa 4 righe (a | b | c | d) invece di 3 (ab | c | d)
Dan

Oltre a ricevere nuove citazioni come ** "value **. potresti aggiornare se c'è qualche regex per rimuoverlo ..
gks

136

Non scherzare con IFS
Non chiamare il comando esterno

variable=abc,def,ghij
for i in ${variable//,/ }
do
    # call your procedure/other scripts here below
    echo "$i"
done

Utilizzo della manipolazione delle stringhe bash http://www.tldp.org/LDP/abs/html/string-manipulation.html


3
La cosa bella di questo è che puoi annidare più loop usando diversi delimitatori. Buon suggerimento!
Brad Parks

5
Bel consiglio per evitare di fare confusione IFS!
Laimoncijus

13
Piccolo avvertimento: se uno qualsiasi dei valori in $icontiene spazi, verranno divisi (e questo potrebbe essere il motivo per cui è passato come separato da virgole anziché da spazi)
Toby Speight

4
Se una funzione precedente ha modificato l'impostazione IFS, non funziona ... Modificala in questo modo:for i in ${variable//,/$IFS} do; echo "$i"; done
Joep

2
Quando provo questo approccio ricevo il messaggio di errore "Sostituzione errata".
Lost Crotchet

55

Se imposti un separatore di campo diverso, puoi utilizzare direttamente un forciclo:

IFS=","
for v in $variable
do
   # things with "$v" ...
done

È inoltre possibile memorizzare i valori in un array e quindi eseguire il ciclo come indicato in Come dividere una stringa su un delimitatore in Bash? :

IFS=, read -ra values <<< "$variable"
for v in "${values[@]}"
do
   # things with "$v"
done

Test

$ variable="abc,def,ghij"
$ IFS=","
$ for v in $variable
> do
> echo "var is $v"
> done
var is abc
var is def
var is ghij

Puoi trovare un approccio più ampio in questa soluzione a Come scorrere un elenco separato da virgole ed eseguire un comando per ogni voce .

Esempi sul secondo approccio:

$ IFS=, read -ra vals <<< "abc,def,ghij"
$ printf "%s\n" "${vals[@]}"
abc
def
ghij
$ for v in "${vals[@]}"; do echo "$v --"; done
abc --
def --
ghij --

Vedi anche stackoverflow.com/questions/918886/… :-) Buona fortuna a tutti.
bombardiere

Sì! Ho pensato anche a questo, solo che poi sono necessari passaggi: a whileper leggere in un array e un altro per scorrere i risultati.
fedorqui 'SO stop harming'

3
Mi rende felice quando qualcuno sa davvero come usare il guscio, piuttosto che incastrare un mucchio di binutil.
PeterT

devi IFStornare a quello che era prima?
pstanton

1
@fedorqui Lo ha fatto! Questa è l'unica variabile che volevo scorrere e ha reso il mio file yaml più facile da leggere (invece di una variabile di ambiente LONG ha un mucchio di righe)
GammaGames

5
#/bin/bash   
TESTSTR="abc,def,ghij"

for i in $(echo $TESTSTR | tr ',' '\n')
do
echo $i
done

Preferisco usare tr invece di sed, perché in alcuni casi sed ha problemi con caratteri speciali come \ r \ n.

l'altra soluzione è impostare IFS su un certo separatore


1

Ecco una soluzione alternativa basata su tr che non usa l'eco, espressa come una riga.

for v in $(tr ',' '\n' <<< "$var") ; do something_with "$v" ; done

Sembra più ordinato senza eco, ma è solo una mia preferenza personale.


0

Prova questo.

#/bin/bash   
testpid="abc,def,ghij" 
count=`echo $testpid | grep -o ',' | wc -l` # this is not a good way
count=`expr $count + 1` 
while [ $count -gt 0 ]  ; do
     echo $testpid | cut -d ',' -f $i
     count=`expr $count - 1 `
done

ma cosa succede se non sono sicuro del numero di campi nella variabile testpid?
Ramanathan K

Inoltre, ho bisogno di passare ogni file della variabile sopra come argomento per una procedura. E voglio farlo fino a quando la lunghezza della variabile diventa zero. (cioè; tutti i campi devono essere passati una volta alla procedura per l'elaborazione)
Ramanathan K

Non so della procedura. Da questo link puoi ottenere il conteggio dei campi. http://stackoverflow.com/questions/8629330/unix-count-of-columns-in-file. Dopodiché, crea il ciclo for nel ciclo while. Puoi prenderlo.
Karthikeyan,

Questo per contare i campi da un file immagino. E se avessi bisogno di contare i campi in una variabile (supponiamo che la variabile sia testpid = abc, def, ghij)
Ramanathan K

echo la variabile e invia l'output a awk. Oppure vedi la mia risposta aggiornata. Può essere utile.
Karthikeyan,

0

Un'altra soluzione che non utilizza IFS e conserva comunque gli spazi:

$ var="a bc,def,ghij"
$ while read line; do echo line="$line"; done < <(echo "$var" | tr ',' '\n')
line=a bc
line=def
line=ghij

Funziona correttamente in bash, tuttavia zsh ingoia gli spazi.
lleaff

0

Ecco la mia soluzione bash pura che non cambia IFS e può includere un delimitatore regex personalizzato.

loop_custom_delimited() {
    local list=$1
    local delimiter=$2
    local item
    if [[ $delimiter != ' ' ]]; then
        list=$(echo $list | sed 's/ /'`echo -e "\010"`'/g' | sed -E "s/$delimiter/ /g")
    fi
    for item in $list; do
        item=$(echo $item | sed 's/'`echo -e "\010"`'/ /g')
        echo "$item"
    done
}
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.