Nama variabel gabungan dereference

12

Saya bisa melakukan ini, tetapi perlu membuat string dari variabel kemudian dereferencing. Apakah ada cara untuk mempersingkatnya menjadi pernyataan yang lebih sederhana?

#!/bin/bash

FRUITS="BANANA APPLE ORANGE"

BANANA_COLOUR="Yellow"
APPLE_COLOUR="Green or Red"
ORANGE_COLOUR="Blue"

for fruit in $( echo $FRUITS ); do
    fruit_colour="${fruit}_COLOUR"
    echo $fruit is ${!fruit_colour}
done

Saya telah mencoba banyak hal seperti ${!"${fruit}_COLOUR"}atau \$${fruit}_COLOURdan banyak varian lainnya, tetapi satu-satunya cara yang berhasil adalah dengan menggunakan string.

Tikar
sumber
The !sintaks adalah bendera pada substitusi variabel yang pada dasarnya mengatakan “pengganti dua kali”. Itu tidak mengubah persyaratan bahwa apa yang ada di dalamnya ${…}harus berupa nama variabel. Jadi tidak, Anda tidak dapat menghindari menggunakan variabel yang berisi nama, kecuali jika Anda menggunakan metode yang berbeda ( eval).
Gilles 'SO- stop being evil'
Anda juga dapat menggunakan array asosiatif untuk melakukan ini dengan lebih bersih. Namun itu bukan jawaban langsung untuk pertanyaan Anda, jadi tambahkan saja sebagai komentar.
Patrick

Jawaban:

10

Off pertama, Anda tidak perlu menggunakan $(echo $FRUITS)dalam forpernyataan. Menggunakan keadilan saja $FRUITSsudah cukup. Kemudian Anda bisa menghilangkan salah satu garis di dalam loop, dengan menggunakan eval.

The evalhanya memberitahu bash untuk membuat kedua evaluasi dari pernyataan berikut (yaitu satu lebih dari evaluasi normal). Yang \$bertahan evaluasi pertama sebagai $, dan evaluasi berikutnya kemudian memperlakukan ini $sebagai awal nama variabel, yang memutuskan untuk "Kuning", dll.

Dengan cara ini Anda tidak perlu memiliki langkah terpisah yang membuat string sementara (yang menurut saya adalah tujuan utama pertanyaan Anda).

for fruit in $FRUITS ;do
    eval echo $fruit is \$${fruit}_COLOUR
done

Untuk metode alternatif, seperti yang disebutkan oleh Patrick dalam komentar (di atas), Anda bisa menggunakan array asosiatif , di mana indeks elemen tidak perlu bilangan bulat. Anda dapat menggunakan string, seperti nama jenis buah. Berikut ini contohnya, menggunakan array asosiatif bash:

# This declares an associative array (It unsets it if it already exists)
declare -A colour
colour['BANANA']="Yellow"
colour["APPLE"]="Green or Red"
colour[MARTIAN ORANGE]="Blue"

for fruit in BANANA APPLE "MARTIAN ORANGE" ;do 
    echo "$fruit" is "${colour[$fruit]}"
done
Peter.O
sumber
6

Anda dapat menggunakan bash-builtin evaluntuk melakukan itu:

#!/bin/bash
FRUITS="BANANA APPLE ORANGE"
BANANA_COLOUR="Yellow"
APPLE_COLOUR="Green or Red"
ORANGE_COLOUR="Blue"

for fruit in $( echo $FRUITS );
do
    fruit_colour=${fruit}_COLOUR
    eval echo $fruit is \$${fruit_colour}
done

Perhatikan tanda dolar yang terbalik. Pada dasarnya, garis "eval" menyebabkan bash pengganti$fruit dan ${fruit_color}, kemudian menggunakan evaluntuk melakukan putaran kedua substitusi sebelum memanggil echo.

Bruce Ediger
sumber
1

Anda tidak perlu pernyataan eval. evaladalah petunjuk dalam sebagian besar bahasa bahwa Anda melakukan sesuatu yang salah.

foo_var=10
bar_var=12

# Build a string with your var name of choice
chosen_prefix='foo'
var_name="${chosen_prefix}_var"

# Use bang to deference it
chossen_value=${!var_name}
echo "Chossen value: ${chossen_value}"
Joseph Lust
sumber
Saya berpendapat itu lebih aman untuk digunakan evalkarena setidaknya orang tahu itu berbahaya, ${!var}sama berbahayanya dengan bash(juga kerentanan perintah injeksi jika $chosen_prefixberada di bawah kendali penyerang) dan lebih sedikit orang yang menyadarinya. Coba var_name='a[$(uname>&2)0]' bash -c 'v=${!var_name}'. Pendekatan terbaik di sini adalah array asosiatif.
Stéphane Chazelas