Zapętlić krotki w bash?


88

Czy można zapętlić krotki w bash?

Na przykład byłoby świetnie, gdyby zadziałały:

for (i,j) in ((c,3), (e,5)); do echo "$i and $j"; done

Czy istnieje obejście, które w jakiś sposób pozwala mi zapętlić krotki?


4
W kontekście języka Python jest to naprawdę bardzo przydatne pytanie!
John Jiang,

5
patrząc na to cztery lata później, zastanawiam się, czy nie ma lepszego sposobu na zrobienie tego. o mój Boże.
Giszmo

Prawie 8 lat później zastanawiałem się, czy nie ma na to lepszego sposobu. Ale ta odpowiedź 2018 wygląda dla mnie całkiem nieźle: stackoverflow.com/a/52228219/463994
MountainX

Odpowiedzi:


87
$ for i in c,3 e,5; do IFS=","; set -- $i; echo $1 and $2; done
c and 3
e and 5

O tym wykorzystaniu set(od man builtins):

Wszelkie argumenty pozostałe po przetworzeniu opcji są traktowane jako wartości parametrów pozycyjnych i są przypisywane kolejno do $ 1, $ 2, ... $ n

Te IFS=","zestawy separator pól więc każdy $idostaje podzielony $1i$2 prawidłowo.

Za pośrednictwem tego bloga .

Edycja: bardziej poprawna wersja, zgodnie z sugestią @SLACEDIAMOND:

$ OLDIFS=$IFS; IFS=','; for i in c,3 e,5; do set -- $i; echo $1 and $2; done; IFS=$OLDIFS
c and 3
e and 5

7
Fajnie - po prostu chcę zaznaczyć, że IFSpowinien zostać zapisany i zresetowany do pierwotnej wartości, jeśli jest to uruchamiane w wierszu poleceń. Ponadto nowy IFSmoże być ustawiony raz, przed uruchomieniem pętli, a nie przy każdej iteracji.
Eggxactly

1
Jeśli którykolwiek z $ i zaczyna się od łącznika, bezpieczniej jestset -- $i
glenn jackman

1
Zamiast oszczędności IFS, tylko ustawić go na setkomendę: for i in c,3 e,5; do IFS="," set -- $i; echo $1 and $2; done. Edytuj swoją odpowiedź: Jeśli wszyscy czytelnicy wybiorą tylko jedno z wymienionych rozwiązań, nie ma sensu czytać pełnej historii rozwoju. Dzięki za tę fajną sztuczkę!
cfi

Jeśli zadeklaruję tuples="a,1 b,2 c,3"i wstawię IFS=','tak, jak w redagowanej wersji, a zamiast tego c,3 e,5użyć $tuples, wcale nie drukuje się dobrze. Ale zamiast tego, jeśli wstawię w pętli for IFS=','tuż po dosłowie kluczowym, działa to dobrze, gdy używam $tupleswartości litteral. Pomyślałem, że warto to powiedzieć.
Simonlbc

@Simonlbc to dlatego, że pętla for używa IFSdo dzielenia iteracji. tj Jeżeli pętla na tablicy jak arr=("c,3" "e,5")i postawić IFSprzed pętli, wartość $ibędzie tylko ci ebędzie podzielony dala 3i 5tak setnie będzie analizować poprawnie, ponieważ $inie będą mieli nic do analizowania. Oznacza to, że jeśli wartości do iteracji nie są wstawiane, to IFSnależy umieścić wewnątrz pętli, a wartość zewnętrzna powinna uwzględniać zamierzony separator zmiennej, po której ma być iterowana. W takich przypadkach $tuplespowinno być po prostu IFS=to, co jest domyślne i dzieli się na białe znaki.
2017

25

Uważam, że to rozwiązanie jest trochę czystsze niż inne, które zostały przesłane, h / t do tego przewodnika po stylu basha, aby zilustrować, jak można użyć odczytu do dzielenia ciągów na separatorze i przypisywania ich do poszczególnych zmiennych.

for i in c,3 e,5; do 
    IFS=',' read item1 item2 <<< "${i}"
    echo "${item1}" and "${item2}"
done

17

W oparciu o odpowiedź udzieloną przez @ eduardo-ivanec bez ustawiania / resetowania IFS, można po prostu zrobić:

for i in "c 3" "e 5"
do
    set -- $i
    echo $1 and $2
done

Wyjście:

c and 3
e and 5

Takie podejście wydaje mi się o wiele prostsze niż podejście akceptowane i cieszące się największą popularnością. Czy jest jakiś powód, aby nie robić tego w ten sposób, w przeciwieństwie do tego, co sugerował @Eduardo Ivanec?
spurra

@spurra to odpowiedź od 6 lat i ½ nowsza i oparta na niej. Kredyt tam, gdzie jest należny.
Diego

1
@Diego Jestem tego świadomy. Jest to wyraźnie zapisane w odpowiedzi. Pytałem, czy jest jakikolwiek powód, aby nie stosować tego podejścia w stosunku do zaakceptowanej odpowiedzi.
spurra

2
@spurra, chciałbyś użyć odpowiedzi Eduardo, jeśli domyślny separator (spacja, tabulacja lub nowa linia) nie działa z jakiegoś powodu ( bash.cyberciti.biz/guide/$IFS )
Diego

11

Użyj tablicy asocjacyjnej (znanej również jako Dictionary / hashMap):

declare -A pairs=(
  [c]=3
  [e]=5
)
for key in "${!pairs[@]}"; do
  value="${pairs[$key]}"
  echo "key is $key and value is $value"
done

Działa dla bash4.0 +.


Jeśli potrzebujesz trójek zamiast par, możesz zastosować bardziej ogólne podejście:

animals=(dog cat mouse)
declare -A sound=(
  [dog]=barks
  [cat]=purrs
  [mouse]=cheeps
)
declare -A size=(
  [dog]=big
  [cat]=medium
  [mouse]=small
)
for animal in "${animals[@]}"; do
  echo "$animal ${sound[$animal]} and it is ${size[$animal]}"
done

FYI, to nie działa dla mnie na Macu z GNU bash, version 4.4.23(1)-release-(x86_64-apple-darwin17.5.0), który został zainstalowany przez brew, więc YMMV.
David

działało jednak na GNU bash, version 4.3.11(1)-release-(x86_64-pc-linux-gnu)Ubuntu 14.04 w kontenerze docker.
David

Wygląda na to, że starsze wersje basha lub te, które nie obsługują tej funkcji, działają poza indeksowaniem? gdzie klucz jest liczbą, a nie ciągiem. tldp.org/LDP/abs/html/declareref.html , a zamiast tego -Amamy -a.
David

David, wydaje się, że tak. Myślę, że możesz spróbować indeksów tablicowych, aby uzyskać „asocjatywność”. Jak declare -a indices=(1 2 3); declare -a sound=(barks purrs cheeps); declare -a size=(big medium small)itp. Nie próbowałem jeszcze tego w terminalu, ale myślę, że powinno działać.
VasiliNovikov

7
c=('a' 'c')
n=(3    4 )

for i in $(seq 0 $((${#c[*]}-1)))
do
    echo ${c[i]} ${n[i]}
done

Czasami może być bardziej przydatne.

Aby wyjaśnić uglyczęść, jak zaznaczono w komentarzach:

seq 0 2 tworzy sekwencję liczb 0 1 2. $ (cmd) jest podstawieniem poleceń, więc w tym przykładzie wynikiem seq 0 2jest sekwencja liczb. Ale jaka jest górna granica $((${#c[*]}-1))?

$ ((coś)) jest rozwinięciem arytmetycznym, więc $ ((3 + 4)) to 7 itd. Nasze wyrażenie to ${#c[*]}-1, więc coś - 1. Całkiem proste, jeśli wiemy, co ${#c[*]}to jest.

c to tablica, c [*] to po prostu cała tablica, $ {# c [*]} to rozmiar tablicy, który w naszym przypadku wynosi 2. Teraz cofamy wszystko: for i in $(seq 0 $((${#c[*]}-1)))jest for i in $(seq 0 $((2-1)))to for i in $(seq 0 1)jest for i in 0 1. Ponieważ ostatni element tablicy ma indeks, który jest długością tablicy Array - 1.


1
powinieneś zrobićfor i in $(seq 0 $(($#c[*]}-1))); do [...]
reox

1
Wow, to wygrywa nagrodę „Najbrzydsza grupa arbitralnych postaci, jakie widziałem dzisiaj”. Czy ktoś chciałby wyjaśnić, co dokładnie robi ta obrzydliwość? Zgubiłem się pod znakiem krzyżyka ...
koniiiik

1
@koniiiik: Dodano wyjaśnienie.
użytkownik nieznany

6
$ echo 'c,3;e,5;' | while IFS=',' read -d';' i j; do echo "$i and $j"; done
c and 3
e and 5

3

Używanie GNU równolegle:

parallel echo {1} and {2} ::: c e :::+ 3 5

Lub:

parallel -N2 echo {1} and {2} ::: c 3 e 5

Lub:

parallel --colsep , echo {1} and {2} ::: c,3 e,5

1
Nie kochasz tego? dobrze sprawiło, że pokonałem bezwładność i zainstalowałemgnu parallel
StephenBoesch

2
brew install parallel
StephenBoesch

2

Stosowanie printfw zastępowaniu procesów:

while read -r k v; do
    echo "Key $k has value: $v"
done < <(printf '%s\n' 'key1 val1' 'key2 val2' 'key3 val3')

Key key1 has value: val1
Key key2 has value: val2
Key key3 has value: val3

Powyższe wymaga bash. Jeśli bashnie jest używany, użyj prostego potoku:

printf '%s\n' 'key1 val1' 'key2 val2' 'key3 val3' |
while read -r k v; do echo "Key $k has value: $v"; done

1
Tak! Anubhava, słodki geniuszu!
Scott

1
do echo $key $value
done < file_discriptor

na przykład:

$ while read key value; do echo $key $value ;done <<EOF
> c 3
> e 5
> EOF
c 3
e 5

$ echo -e 'c 3\ne 5' > file

$ while read key value; do echo $key $value ;done <file
c 3
e 5

$ echo -e 'c,3\ne,5' > file

$ while IFS=, read key value; do echo $key $value ;done <file
c 3
e 5

0

Trochę bardziej zaangażowany, ale może być przydatny:

a='((c,3), (e,5))'
IFS='()'; for t in $a; do [ -n "$t" ] && { IFS=','; set -- $t; [ -n "$1" ] && echo i=$1 j=$2; }; done

0

Ale co, jeśli krotka jest większa niż k / v, które może pomieścić tablica asocjacyjna? A jeśli to 3 lub 4 elementy? Można by rozwinąć tę koncepcję:

###---------------------------------------------------
### VARIABLES
###---------------------------------------------------
myVars=(
    'ya1,ya2,ya3,ya4'
    'ye1,ye2,ye3,ye4'
    'yo1,yo2,yo3,yo4'
    )


###---------------------------------------------------
### MAIN PROGRAM
###---------------------------------------------------
### Echo all elements in the array
###---
printf '\n\n%s\n' "Print all elements in the array..."
for dataRow in "${myVars[@]}"; do
    while IFS=',' read -r var1 var2 var3 var4; do
        printf '%s\n' "$var1 - $var2 - $var3 - $var4"
    done <<< "$dataRow"
done

Wtedy wynik wyglądałby mniej więcej tak:

$ ./assoc-array-tinkering.sh 

Print all elements in the array...
ya1 - ya2 - ya3 - ya4
ye1 - ye2 - ye3 - ye4
yo1 - yo2 - yo3 - yo4

A liczba elementów jest teraz nieograniczona. Nie szukam głosów; po prostu głośno myślę. REF1 , REF2


Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.