Di luar array asosiatif, ada beberapa cara untuk mencapai variabel dinamis di Bash. Perhatikan bahwa semua teknik ini menghadirkan risiko, yang dibahas pada akhir jawaban ini.
Dalam contoh berikut ini saya akan menganggap itu i=37
dan bahwa Anda ingin alias variabel bernama var_37
yang nilai awalnya adalah lolilol
.
Metode 1. Menggunakan variabel "pointer"
Anda cukup menyimpan nama variabel dalam variabel tipuan, tidak seperti pointer C. Bash kemudian memiliki sintaks untuk membaca variabel alias: ${!name}
memperluas ke nilai variabel yang namanya adalah nilai variabel name
. Anda dapat menganggapnya sebagai ekspansi dua tahap: ${!name}
mengembang ke $var_37
, yang mengembang ke lolilol
.
name="var_$i"
echo "$name" # outputs “var_37”
echo "${!name}" # outputs “lolilol”
echo "${!name%lol}" # outputs “loli”
# etc.
Sayangnya, tidak ada sintaks counterpart untuk memodifikasi variabel alias. Sebagai gantinya, Anda dapat mencapai tugas dengan salah satu trik berikut.
1a. Menugaskan denganeval
eval
itu jahat, tetapi juga cara paling sederhana dan paling portabel untuk mencapai tujuan kita. Anda harus keluar dengan hati-hati dari sisi kanan penugasan, karena akan dievaluasi dua kali . Cara mudah dan sistematis untuk melakukan ini adalah mengevaluasi sisi kanan sebelumnya (atau menggunakan printf %q
).
Dan Anda harus memeriksa secara manual bahwa sisi kiri adalah nama variabel yang valid, atau nama dengan indeks (bagaimana jika itu evil_code #
?). Sebaliknya, semua metode lain di bawah ini memberlakukannya secara otomatis.
# check that name is a valid variable name:
# note: this code does not support variable_name[index]
shopt -s globasciiranges
[[ "$name" == [a-zA-Z_]*([a-zA-Z_0-9]) ]] || exit
value='babibab'
eval "$name"='$value' # carefully escape the right-hand side!
echo "$var_37" # outputs “babibab”
Kerugian:
- tidak memeriksa validitas nama variabel.
eval
itu jahat.
eval
itu jahat.
eval
itu jahat.
1b. Menugaskan denganread
The read
builtin memungkinkan Anda menetapkan nilai-nilai ke variabel yang Anda berikan nama, suatu fakta yang dapat dimanfaatkan dalam hubungannya dengan di sini-string:
IFS= read -r -d '' "$name" <<< 'babibab'
echo "$var_37" # outputs “babibab\n”
Bagian IFS
dan opsi -r
memastikan bahwa nilai ditetapkan apa adanya, sedangkan opsi -d ''
memungkinkan untuk menetapkan nilai multi-baris. Karena opsi terakhir ini, perintah kembali dengan kode keluar yang tidak nol.
Perhatikan bahwa, karena kita menggunakan string di sini, karakter baris baru ditambahkan ke nilai.
Kerugian:
- agak tidak jelas;
- kembali dengan kode keluar bukan nol;
- menambahkan baris baru ke nilai.
1c. Menugaskan denganprintf
Sejak Bash 3.1 (dirilis 2005), printf
builtin juga dapat menetapkan hasilnya ke variabel yang namanya diberikan. Berbeda dengan solusi sebelumnya, ini hanya berfungsi, tidak ada upaya ekstra yang diperlukan untuk menghindari hal-hal, untuk mencegah pemisahan dan sebagainya.
printf -v "$name" '%s' 'babibab'
echo "$var_37" # outputs “babibab”
Kerugian:
- Kurang portabel (tapi, yah).
Metode 2. Menggunakan variabel "referensi"
Sejak Bash 4.3 (dirilis 2014), declare
builtin memiliki opsi -n
untuk membuat variabel yang merupakan "referensi nama" ke variabel lain, seperti referensi C ++. Sama seperti dalam Metode 1, referensi menyimpan nama variabel alias, tetapi setiap kali referensi diakses (baik untuk membaca atau menugaskan), Bash secara otomatis menyelesaikan tipuan.
Selain itu, Bash memiliki khusus dan sintaks yang sangat membingungkan untuk mendapatkan nilai referensi itu sendiri, hakim sendiri: ${!ref}
.
declare -n ref="var_$i"
echo "${!ref}" # outputs “var_37”
echo "$ref" # outputs “lolilol”
ref='babibab'
echo "$var_37" # outputs “babibab”
Ini tidak menghindari jebakan yang dijelaskan di bawah, tetapi setidaknya itu membuat sintaks langsung.
Kerugian:
Risiko
Semua teknik aliasing ini menghadirkan beberapa risiko. Yang pertama adalah mengeksekusi kode arbitrer setiap kali Anda menyelesaikan tipuan (baik untuk membaca atau untuk menetapkan) . Memang, alih-alih nama variabel skalar, seperti var_37
, Anda juga dapat alias subscript array, seperti arr[42]
. Tapi Bash mengevaluasi isi tanda kurung setiap kali dibutuhkan, jadi aliasing arr[$(do_evil)]
akan memiliki efek yang tak terduga ... Sebagai konsekuensinya, hanya menggunakan teknik ini ketika Anda mengontrol asal alias .
function guillemots() {
declare -n var="$1"
var="«${var}»"
}
arr=( aaa bbb ccc )
guillemots 'arr[1]' # modifies the second cell of the array, as expected
guillemots 'arr[$(date>>date.out)1]' # writes twice into date.out
# (once when expanding var, once when assigning to it)
Risiko kedua adalah membuat alias siklik. Karena variabel Bash diidentifikasi dengan nama mereka dan bukan oleh cakupannya, Anda dapat secara tidak sengaja membuat alias untuk dirinya sendiri (sambil berpikir itu akan alias variabel dari cakupan yang melampirkan). Ini dapat terjadi khususnya ketika menggunakan nama variabel umum (seperti var
). Sebagai konsekuensinya, hanya gunakan teknik-teknik ini ketika Anda mengontrol nama variabel alias .
function guillemots() {
# var is intended to be local to the function,
# aliasing a variable which comes from outside
declare -n var="$1"
var="«${var}»"
}
var='lolilol'
guillemots var # Bash warnings: “var: circular name reference”
echo "$var" # outputs anything!
Sumber: