Bash'de tuples üzerinden döngü?


89

Bash'de tuple'ları döngülemek mümkün mü?

Örnek olarak, aşağıdakilerin işe yaraması harika olurdu:

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

Bir şekilde demetlerin üzerinden geçmeme izin veren bir çözüm var mı?


4
Python arka planından geliyorsa, bu gerçekten çok faydalı bir sorudur!
John Jiang

5
buna dört yıl sonra baktığımda, bunu yapmanın daha iyi bir yolu olup olmadığını merak ediyorum. Aman Tanrım.
Giszmo

Neredeyse 8 yıl sonra, bunu yapmanın daha iyi bir yolu olup olmadığını da merak ettim. Ancak bu 2018 cevabı bana oldukça iyi görünüyor: stackoverflow.com/a/52228219/463994
MountainX

Yanıtlar:


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

Bu set(kimden man builtins) kullanımı hakkında:

Seçenek işlemeden sonra kalan bağımsız değişkenler, konumsal parametreler için değerler olarak kabul edilir ve sırasıyla $ 1, $ 2, ... $ n olarak atanır

IFS=","Her sözüm alan ayırıcı setleri $ibölünür alır $1ve$2 düzgün.

Via bu blog .

Düzenleme: @SLACEDIAMOND tarafından önerildiği gibi daha doğru sürüm:

$ 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
Güzel - sadece IFSkomut satırında çalıştırılırsa kaydedilmesi ve orijinal değerine sıfırlanması gerektiğini belirtmek istiyorum . Ayrıca yeni IFS, her yinelemeden ziyade döngü çalışmadan önce bir kez ayarlanabilir.
Eggxactly

1
$ İ'den herhangi birinin kısa çizgiyle başlaması durumunda, daha güvenlidirset -- $i
glenn jackman

1
Yerine tasarrufu IFS, sadece ayarlıyorum setkomuta: for i in c,3 e,5; do IFS="," set -- $i; echo $1 and $2; done. Lütfen cevabınızı düzenleyin: Tüm okuyucular listelenen çözümlerden yalnızca birini seçerse, tam geliştirme geçmişini okumak zorunda kalmanın bir anlamı yoktur. Bu harika numara için teşekkürler!
cfi

Düzenlenmiş sürümde olduğu gibi beyan tuples="a,1 b,2 c,3"edip koyarsam IFS=','ve c,3 e,5kullanmak yerine $tupleshiç iyi yazdırmaz. Ancak bunun yerine , for döngüsünde anahtar sözcüğün IFS=','hemen sonrasına koyarsam do, $tupleshem literal değerleri hem de kullanırken iyi çalışır . Söylemeye değer olduğunu düşündüm.
Simonlbc

@Simonlbc bunun nedeni for döngüsünün IFSyinelemeleri bölmek için kullanılmasıdır . Eğer böyle bir diziye üzerinde döngü halinde yani arr=("c,3" "e,5")ve koyun IFSdöngü için önce, değeri $isadece olacak cve ebu uzak bölecektir 3ve 5böylece setçünkü doğru ayrıştırma olacak $iayrıştırma şey olmaz. Bu, yinelenecek değerler satır içi değilse IFS, döngünün içine yerleştirilmesi gerektiği ve dış değerin, değişkenin yinelemesi için amaçlanan ayırıcıya uyması gerektiği anlamına gelir. Böyle durumlarda , varsayılan olan ve boşluklara bölünen $tuplesbasit olmalıdır IFS=.
2017

26

Bu çözümün sunulan diğer çözümlerden biraz daha temiz olduğuna inanıyorum, bu bash stili kılavuza, okumanın bir sınırlayıcıda dizeleri ayırmak ve bunları ayrı değişkenlere atamak için nasıl kullanılabileceğini göstermek için h / t .

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

20

@ Eduardo-ivanec tarafından, 'yi ayarlamadan / sıfırlamadan verilen cevaba dayanarak, IFSbasitçe şunları yapabilirsiniz:

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

Çıktı:

c and 3
e and 5

Bu yaklaşım bana, kabul edilen ve en çok beğenilen yaklaşımdan çok daha basit görünüyor. @Eduardo Ivanec'in önerdiğinin aksine bunu bu şekilde yapmamak için herhangi bir sebep var mı?
spurra

@spurra bu cevap 6 yıl ve recent daha güncel ve buna dayanıyor. Zamanı geldiği yerde kredi.
Diego

1
@Diego Bunun farkındayım. Cevapta açıkça yazılmıştır. Kabul edilen cevap yerine bu yaklaşımı kullanmamak için herhangi bir neden var mı diye sordum.
spurra

2
@spurra Varsayılan ayırıcı (boşluk, sekme veya satırsonu) herhangi bir nedenle sizin için işe yaramazsa ( bash.cyberciti.biz/guide/$IFS )
Diego

13

İlişkilendirilebilir dizi kullanın (sözlük / hashMap olarak da bilinir):

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

Bash4.0 + için çalışır.


Çiftler yerine üçlülere ihtiyacınız varsa, daha genel yaklaşımı kullanabilirsiniz:

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

Bilginize, bu benim için GNU bash, version 4.4.23(1)-release-(x86_64-apple-darwin17.5.0)brew aracılığıyla kurulan Mac'te işe yaramadı, bu yüzden YMMV.
David

ancak GNU bash, version 4.3.11(1)-release-(x86_64-pc-linux-gnu)docker container içinde Ubuntu 14.04'ten çalıştı.
David

bash'ın eski sürümleri veya bu özelliği desteklemeyen sürümler gibi görünüyor mu? burada anahtar, dizeden ziyade bir sayıdır. tldp.org/LDP/abs/html/declareref.html ve onun yerine -Avar -a.
David

David, öyle görünüyor. O zaman "çağrışım" elde etmek için dizi indekslerini deneyebileceğinizi düşünüyorum. Gibi declare -a indices=(1 2 3); declare -a sound=(barks purrs cheeps); declare -a size=(big medium small)vb. Henüz terminalde denemedim, ama işe yaraması gerektiğini düşünüyorum.
VasiliNovikov

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

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

Bazen daha kullanışlı olabilir.

uglyYorumlarda belirtildiği gibi kısmı açıklamak için :

seq 0 2 , 0 1 2 sayılarının dizisini üretir. $ (cmd), komut ikamesidir, bu nedenle bu örnek için çıktısı seq 0 2, sayı dizisi olan. Ama üst sınır $((${#c[*]}-1))nedir?

$ ((somthing)) aritmetik açılımdır, yani $ (((3 + 4)) 7'dir vs. Bizim İfademiz ${#c[*]}-1, yani bir şey - 1. Ne ${#c[*]}olduğunu bilirsek oldukça basit .

c bir dizidir, c [*] yalnızca tüm dizidir, $ {# c [*]} bizim durumumuzda 2 olan dizinin boyutudur. Şimdi herşeyi geri rulo: for i in $(seq 0 $((${#c[*]}-1)))olduğunu for i in $(seq 0 $((2-1)))olduğunu for i in $(seq 0 1)olduğunu for i in 0 1. Çünkü dizideki son eleman, Dizi - 1'in uzunluğu olan bir dizine sahiptir.


1
yapmalısınfor i in $(seq 0 $(($#c[*]}-1))); do [...]
2013

1
Vay canına, bu "Bugün Gördüğüm En Çirkin Keyfi Karakter Grubu" ödülünü kazandı. Bu iğrençliğin tam olarak ne yaptığını açıklamak isteyen var mı? Hash işaretinde kayboldum ...
koniiiik

1
@koniiiik: Açıklama eklendi.
kullanıcı bilinmiyor

"Neu-Perl: İntikam" Perl'de aslında daha net olacağını düşünüyorum. :-)
Buzz Moschetti

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

GNU Parallel Kullanımı:

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

Veya:

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

Veya:

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

1
Buna aşk yok mu? İyi yapılmış beni atalet üstesinden gelmek ve yükleyingnu parallel
StephenBoesch

2
brew install parallel
StephenBoesch

2

printfBir işlem ikamesinde kullanmak :

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

Yukarıdakiler gerektirir bash. Kullanılmıyorsa bash, basit ardışık düzen kullanın:

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

1
Evet! Anubhava, seni tatlı dahi!
Scott

1
do echo $key $value
done < file_discriptor

Örneğin:

$ 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

Biraz daha karmaşık, ancak faydalı olabilir:

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

Peki ya demet, ilişkilendirilebilir dizinin tutabileceği k / v değerinden büyükse? Ya 3 veya 4 elementse? Bu kavramı genişletebiliriz:

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

Ardından çıktı şuna benzer görünecektir:

$ ./assoc-array-tinkering.sh 

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

Ve eleman sayısı artık sınırsızdır. Oy aramamak; sadece yüksek sesle düşünüyorum. REF1 , REF2


Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.