Przejdź przez zmienną powłoki oddzieloną przecinkami

109

Załóżmy, że mam zmienną powłoki Unix, jak poniżej

variable=abc,def,ghij

I Aby wyodrębnić wszystkie wartości ( abc, defi ghij) za pomocą pętli i przechodzi każdą wartość w procedurze.

Skrypt powinien umożliwiać wyodrębnienie dowolnej liczby wartości oddzielonych przecinkami z $variable.

Ramanathan K.
źródło
Co dokładnie chcesz zrobić, powtarzając iterację?
SMA
1
Chcę przekazać każde pole (powiedzmy abc) jako argument do procedury.
Ramanathan K,

Odpowiedzi:

125

Możesz użyć następującego skryptu do dynamicznego przechodzenia przez zmienną, bez względu na to, ile ma pól, o ile jest oddzielona przecinkami.

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

Zamiast echo "$i"powyższego wywołania, pomiędzy doi donewewnątrz pętli for, możesz wywołać swoją procedurę proc "$i".


Aktualizacja : powyższy fragment działa, jeśli wartość zmiennej nie zawiera spacji. Jeśli masz takie wymaganie, użyj jednego z rozwiązań, które mogą się zmienić, IFSa następnie przeanalizuj zmienną.


Mam nadzieję że to pomoże.

anakron
źródło
8
To nie działa, jeśli $variablezawiera białe znaki, np. variable=a b,c,d=> Wyświetla 4 linie (a | b | c | d) zamiast 3 (ab | c | d)
Dan
Dodawane są nowe cytaty, takie jak ** "value **. czy mógłbyś zaktualizować, jeśli istnieje jakieś wyrażenie regularne, aby to usunąć ...
gks
136

Nie zadziera się z IFS
Nie wywołuje polecenia zewnętrznego

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

Korzystanie z manipulacji ciągami basha http://www.tldp.org/LDP/abs/html/string-manipulation.html

neric
źródło
3
Zaletą tego jest to, że możesz zagnieżdżać wiele pętli za pomocą różnych ograniczników. Dobry pomysł!
Brad Parks,
5
Dobra wskazówka, jak uniknąć bałaganu IFS!
Laimoncijus,
13
Małe ostrzeżenie - jeśli którakolwiek z wartości $izawiera spacje, zostanie podzielona (i może to być powód, dla którego jest przekazywana jako oddzielona przecinkami, a nie spacjami)
Toby Speight
4
Jeśli poprzednia funkcja zmieniła ustawienie IFS, to nie działa ... Zmień to na:for i in ${variable//,/$IFS} do; echo "$i"; done
Joep
2
Gdy próbuję tego podejścia, pojawia się komunikat o błędzie „Złe podstawienie”.
Lost Crotchet
55

Jeśli ustawisz inny separator pól, możesz bezpośrednio użyć forpętli:

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

Możesz również przechowywać wartości w tablicy, a następnie przeglądać je w pętli, jak wskazano w Jak podzielić ciąg na separatorze w 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

W tym rozwiązaniu można znaleźć szersze podejście do sposobu iteracji po liście oddzielonej przecinkami i wykonywania polecenia dla każdego wpisu .

Przykłady drugiego podejścia:

$ 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 --
fedorqui 'SO przestań szkodzić'
źródło
Zobacz także stackoverflow.com/questions/918886/… :-) Powodzenia wszystkim.
łowca
Tak! Myślałem też o tym, tyle że wtedy wymagało to wykonania kroków: a whiledo wczytania do tablicy i kolejnego do zapętlenia wyników.
fedorqui 'SO przestań szkodzić'
3
Cieszy mnie, gdy ktoś naprawdę wie, jak używać muszli, zamiast połączyć razem kilka binutilów.
PeterT
czy musisz IFSwrócić do tego, co było wcześniej?
pstanton
1
@fedorqui Zrobiło się! To jedyna zmienna, którą chciałem przepętać i dzięki niej mój plik yaml był łatwiejszy do odczytania (zamiast jednej zmiennej środowiskowej LONG ma kilka wierszy)
GammaGames
5
#/bin/bash   
TESTSTR="abc,def,ghij"

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

Wolę używać tr zamiast sed, ponieważ sed ma w niektórych przypadkach problemy ze specjalnymi znakami, takimi jak \ r \ n.

innym rozwiązaniem jest ustawienie IFS na określony separator

Marek Lisiecki
źródło
1

Oto alternatywne rozwiązanie oparte na tr, które nie używa echa, wyrażone jako jednowierszowe.

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

Wydaje się bardziej uporządkowane bez echa, ale to tylko moje osobiste preferencje.

6EQUJ5
źródło
0

Spróbuj tego.

#/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
Karthikeyan.RS
źródło
ale co, jeśli nie jestem pewien liczby pól w zmiennej testpid?
Ramanathan K,
Ponadto muszę przekazać każde pole powyższej zmiennej jako argument do procedury. I chcę to robić, aż długość zmiennej osiągnie zero. (tzn. wszystkie pola należy przekazać raz do procedury przetwarzania)
Ramanathan K,
Nie wiem jak wygląda procedura. Z tego linku możesz uzyskać liczbę pól. http://stackoverflow.com/questions/8629330/unix-count-of-columns-in-file. Następnie zrób pętlę for w pętlę while. Możesz to zdobyć.
Karthikeyan.RS
Chyba chodzi o policzenie pól z pliku. Co jeśli muszę policzyć pola w zmiennej (przypuśćmy, że zmienna to testpid = abc, def, ghij)
Ramanathan K
wyświetlaj zmienną i potokuj wyjście do awk. Albo zobacz moją zaktualizowaną odpowiedź. Może to się przyda.
Karthikeyan.RS
0

Inne rozwiązanie nie wykorzystujące IFS i nadal zachowujące przestrzenie:

$ var="a bc,def,ghij"
$ while read line; do echo line="$line"; done < <(echo "$var" | tr ',' '\n')
line=a bc
line=def
line=ghij
user3071170
źródło
Działa to poprawnie w bash, jednak zsh połyka spacje.
lleaff
0

Oto moje czyste rozwiązanie bash, które nie zmienia IFS i może przyjmować niestandardowy separator wyrażeń regularnych.

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
}
placidwolverine
źródło