Ulangi tupel dalam pesta?

89

Apakah mungkin untuk mengulang tupel dalam bash?

Sebagai contoh, alangkah baiknya jika yang berikut berhasil:

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

Apakah ada solusi yang memungkinkan saya mengulang tupel?

jujur
sumber
4
Berasal dari latar belakang python, ini adalah pertanyaan yang sangat berguna!
John Jiang
5
melihat ini empat tahun kemudian saya bertanya-tanya apakah masih ada cara yang lebih baik untuk melakukan ini. ya Tuhan.
Giszmo
Hampir 8 tahun kemudian saya juga bertanya-tanya apakah masih ada cara yang lebih baik untuk melakukan ini. Tetapi jawaban tahun 2018 ini terlihat cukup bagus bagi saya: stackoverflow.com/a/52228219/463994
MountainX

Jawaban:

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

Tentang penggunaan set(dari man builtins):

Setiap argumen yang tersisa setelah pemrosesan opsi diperlakukan sebagai nilai untuk parameter posisi dan ditetapkan, secara berurutan, ke $ 1, $ 2, ... $ n

The IFS=","menetapkan pemisah bidang sehingga setiap $iakan tersegmentasi menjadi $1dan $2benar.

Melalui blog ini .

Edit: versi yang lebih benar, seperti yang disarankan oleh @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
Eduardo Ivanec
sumber
7
Bagus - hanya ingin menunjukkan IFSharus disimpan dan direset ke nilai aslinya jika ini dijalankan pada baris perintah. Selain itu, yang baru IFSbisa disetel sekali, sebelum loop dijalankan, bukan setiap iterasi.
Eggxactly
1
Jika ada $ i yang dimulai dengan tanda hubung, lebih aman untukset -- $i
glenn jackman
1
Alih-alih tabungan IFS, hanya mengaturnya untuk setperintah: for i in c,3 e,5; do IFS="," set -- $i; echo $1 and $2; done. Harap edit jawaban Anda: Jika semua pembaca hanya akan memilih satu dari solusi yang terdaftar, tidak ada gunanya membaca sejarah pengembangan lengkap. Terima kasih untuk trik keren ini!
cfi
Jika saya mendeklarasikan tuples="a,1 b,2 c,3"dan meletakkannya IFS=','sebagai versi yang diedit, dan alih-alih c,3 e,5menggunakannya, $tuplesitu tidak dicetak dengan baik sama sekali. Tetapi sebaliknya jika saya meletakkan IFS=','tepat setelah dokata kunci di loop for, itu berfungsi dengan baik saat menggunakan $tuplesserta nilai-nilai litteral. Hanya berpikir itu layak untuk dikatakan.
Simonlbc
@ Simonlbc itu karena perulangan for digunakan IFSuntuk memisahkan iterasi. yaitu Jika Anda loop atas array seperti arr=("c,3" "e,5")dan menempatkan IFSsebelum untuk loop, nilai $iakan hanya cdan e, itu akan memisahkan diri 3dan 5sehingga settidak akan mengurai dengan benar karena $itidak akan memiliki apa pun untuk mengurai. Ini berarti bahwa jika nilai yang akan diiterasi tidak sebaris, nilai tersebut IFSharus dimasukkan ke dalam loop, dan nilai luar harus sesuai dengan pemisah yang dimaksudkan untuk variabel yang akan diiterasi. Dalam kasus $tuplesitu harus sederhana IFS=yang merupakan default dan dibagi berdasarkan spasi.
sampai
26

Saya percaya solusi ini sedikit lebih bersih daripada yang lain yang telah dikirimkan, h / t ke panduan gaya bash ini untuk mengilustrasikan bagaimana read dapat digunakan untuk membagi string pada pembatas dan menetapkannya ke variabel individual.

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

Berdasarkan jawaban yang diberikan oleh @ eduardo-ivanec tanpa mengatur / mengatur ulang IFS, seseorang dapat dengan mudah melakukan:

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

Hasil:

c and 3
e and 5
MZHm
sumber
Bagi saya, pendekatan ini jauh lebih sederhana daripada pendekatan yang diterima dan paling banyak disukai. Apakah ada alasan untuk tidak melakukannya dengan cara ini dibandingkan dengan apa yang disarankan @Eduardo Ivanec?
spurra
@spurra jawaban ini adalah 6 tahun dan ½ lebih baru, dan berdasarkan itu. Kredit saat jatuh tempo.
Diego
1
@Diego Saya menyadari itu. Itu tertulis secara eksplisit dalam jawabannya. Saya bertanya apakah ada alasan untuk tidak menggunakan pendekatan ini atas jawaban yang diterima.
spurra
2
@spurra Anda ingin menggunakan jawaban Eduardo jika pemisah default (spasi, tab, atau baris baru) tidak berfungsi untuk Anda karena alasan tertentu ( bash.cyberciti.biz/guide/$IFS )
Diego
13

Gunakan array asosiatif (juga dikenal sebagai kamus / 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

Bekerja untuk bash4.0 +.


Jika Anda membutuhkan tripel daripada berpasangan, Anda dapat menggunakan pendekatan yang lebih umum:

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
VasiliNovikov
sumber
FYI, ini tidak berhasil untuk saya di Mac dengan GNU bash, version 4.4.23(1)-release-(x86_64-apple-darwin17.5.0), yang diinstal melalui brew, jadi YMMV.
David
Namun itu berfungsi GNU bash, version 4.3.11(1)-release-(x86_64-pc-linux-gnu)dari Ubuntu 14.04 dalam container docker.
David
Sepertinya versi bash yang lebih lama atau yang tidak mendukung fitur ini berfungsi di luar basis indeks? dimana kuncinya adalah angka, bukan string. tldp.org/LDP/abs/html/declareref.html , dan alih-alih -Akami punya -a.
David
David, sepertinya begitu. Saya pikir Anda dapat mencoba indeks array untuk mendapatkan "asosiativitas". Seperti declare -a indices=(1 2 3); declare -a sound=(barks purrs cheeps); declare -a size=(big medium small)dll. Belum mencobanya di terminal, tapi menurut saya itu akan berhasil.
VasiliNovikov
7
c=('a' 'c')
n=(3    4 )

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

Mungkin terkadang lebih berguna.

Untuk menjelaskan uglybagian tersebut, seperti yang dicatat di komentar:

seq 0 2 menghasilkan urutan angka 0 1 2. $ (cmd) adalah perintah substitusi, jadi untuk contoh ini keluarannya seq 0 2, yaitu urutan bilangan. Tapi apa batas atasnya, itu $((${#c[*]}-1))?

$ ((somthing)) adalah ekspansi aritmatika, jadi $ ((3 + 4)) adalah 7 dll. Ekspresi kita adalah ${#c[*]}-1, jadi sesuatu - 1. Cukup sederhana, jika kita tahu apa ${#c[*]}itu.

c adalah sebuah array, c [*] hanyalah seluruh array, $ {# c [*]} adalah ukuran dari array yang dalam kasus kita adalah 2. Sekarang kita roll semuanya kembali: for i in $(seq 0 $((${#c[*]}-1)))adalah for i in $(seq 0 $((2-1)))adalah for i in $(seq 0 1)adalah for i in 0 1. Karena elemen terakhir pada array memiliki indeks yang merupakan panjang dari Array - 1.

Pengguna tidak diketahui
sumber
1
Anda harus melakukannyafor i in $(seq 0 $(($#c[*]}-1))); do [...]
reox
1
Wow, ini memenangkan penghargaan "Kumpulan Karakter Sewenang-wenang yang Paling Buruk yang Saya Lihat Hari Ini". Adakah yang mau menjelaskan apa sebenarnya yang dilakukan kekejian ini? Saya tersesat di tanda hash ...
koniiiik
1
@koniiiik: Penjelasan ditambahkan.
pengguna tidak diketahui
"Neu-Perl: The Revenge" Saya pikir itu sebenarnya akan lebih jelas di perl. :-)
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
kev
sumber
3

Menggunakan GNU Parallel:

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

Atau:

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

Atau:

parallel --colsep , echo {1} and {2} ::: c,3 e,5
Ole Tange
sumber
1
Tidak ada cinta untuk ini? baik membuat saya mengatasi inersia dan menginstalgnu parallel
StephenBoesch
2
brew install parallel
StephenBoesch
2

Menggunakan printfdalam proses substitusi:

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

Di atas membutuhkan bash. Jika bashtidak digunakan maka gunakan pipa sederhana:

printf '%s\n' 'key1 val1' 'key2 val2' 'key3 val3' |
while read -r k v; do echo "Key $k has value: $v"; done
anubhava.dll
sumber
1
Iya! Anubhava, dasar jenius yang manis!
Scott
1
do echo $key $value
done < file_discriptor

sebagai contoh:

$ 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
prodriguez903
sumber
0

Sedikit lebih terlibat, tetapi mungkin berguna:

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

Tetapi bagaimana jika tupel lebih besar dari k / v yang dapat ditampung oleh array asosiatif? Bagaimana jika itu 3 atau 4 elemen? Seseorang dapat mengembangkan konsep ini:

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

Maka hasilnya akan terlihat seperti:

$ ./assoc-array-tinkering.sh 

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

Dan jumlah elemen sekarang tidak terbatas. Tidak mencari suara; hanya berpikir keras. REF1 , REF2

todd_dsm
sumber