Saya bingung tentang skrip bash.
Saya memiliki kode berikut:
function grep_search() {
magic_way_to_define_magic_variable_$1=`ls | tail -1`
echo $magic_variable_$1
}
Saya ingin dapat membuat nama variabel yang berisi argumen pertama dari perintah dan memberikan nilai misalnya baris terakhir ls
.
Jadi untuk menggambarkan apa yang saya inginkan:
$ ls | tail -1
stack-overflow.txt
$ grep_search() open_box
stack-overflow.txt
Jadi, bagaimana saya harus mendefinisikan / menyatakan $magic_way_to_define_magic_variable_$1
dan bagaimana saya harus menyebutnya dalam skrip?
Saya telah mencoba eval
, ${...}
, \$${...}
, tapi aku masih bingung.
Jawaban:
Gunakan array asosiatif, dengan nama perintah sebagai kunci.
Jika Anda tidak dapat menggunakan array asosiatif (misalnya, Anda harus mendukung
bash
3), Anda dapat menggunakandeclare
untuk membuat nama variabel dinamis:dan menggunakan ekspansi parameter tidak langsung untuk mengakses nilai.
Lihat BashFAQ: Tidak Langsung - Mengevaluasi variabel tidak langsung / referensi .
sumber
-a
mendeklarasikan array yang diindeks, bukan array asosiatif. Kecuali jika argumennyagrep_search
adalah angka, itu akan diperlakukan sebagai parameter dengan nilai numerik (yang default ke 0 jika parameter tidak disetel).4.2.45(2)
dan menyatakan tidak mencantumkannya sebagai opsideclare: usage: declare [-afFirtx] [-p] [name[=value] ...]
. Namun tampaknya berfungsi dengan benar.declare -h
di 4.2.45 (2) untuk saya menunjukkandeclare: usage: declare [-aAfFgilrtux] [-p] [name[=value] ...]
. Anda mungkin memeriksa ulang apakah Anda benar-benar menjalankan 4.x dan bukan 3.2.declare $varname="foo"
?${!varname}
jauh lebih sederhana dan kompatibel secara luasSaya telah mencari cara yang lebih baik untuk melakukannya baru-baru ini. Array asosiatif terdengar seperti berlebihan bagi saya. Lihat apa yang kutemukan:
...lalu...
sumber
prefix_${middle}_postfix
(mis. format Anda tidak akan berfungsi untukvarname=$prefix_suffix
)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 bernamavar_37
yang nilai awalnya adalahlolilol
.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 variabelname
. Anda dapat menganggapnya sebagai ekspansi dua tahap:${!name}
mengembang ke$var_37
, yang mengembang kelolilol
.Sayangnya, tidak ada sintaks counterpart untuk memodifikasi variabel alias. Sebagai gantinya, Anda dapat mencapai tugas dengan salah satu trik berikut.
1a. Menugaskan dengan
eval
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 menggunakanprintf %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.Kerugian:
eval
itu jahat.eval
itu jahat.eval
itu jahat.1b. Menugaskan dengan
read
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: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:
1c. Menugaskan dengan
printf
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.Kerugian:
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}
.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, sepertiarr[42]
. Tapi Bash mengevaluasi isi tanda kurung setiap kali dibutuhkan, jadi aliasingarr[$(do_evil)]
akan memiliki efek yang tak terduga ... Sebagai konsekuensinya, hanya menggunakan teknik ini ketika Anda mengontrol asal alias .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 .Sumber:
sumber
${!varname}
teknik ini membutuhkan perantara menengah untukvarname
.Contoh di bawah ini mengembalikan nilai $ name_of_var
sumber
echo
dengan substitusi perintah (yang meleset dari kutipan) tidak perlu. Plus, opsi-n
harus diberikanecho
. Dan, seperti biasa,eval
tidak aman. Tapi semua ini tidak diperlukan karena Bash memiliki lebih aman, sintaks yang lebih jelas dan lebih pendek untuk tujuan ini:${!var}
.Ini seharusnya bekerja:
sumber
Ini juga akan berhasil
Dalam kasus Anda
sumber
Sebagai per BashFAQ / 006 , Anda dapat menggunakan
read
dengan di sini sintaks tali untuk menetapkan variabel tidak langsung:Pemakaian:
sumber
Menggunakan
declare
Tidak perlu menggunakan awalan seperti pada jawaban lain, tidak ada array. Gunakan adil
declare
, kutip ganda , dan ekspansi parameter .Saya sering menggunakan trik berikut untuk mem-parsing daftar
one to n
argumen yang berisi argumen yang diformat sebagaikey=value otherkey=othervalue etc=etc
, Seperti:Tetapi memperluas daftar argv suka
Kiat ekstra
sumber
printf
ataueval
Wow, sebagian besar sintaksnya mengerikan! Berikut adalah satu solusi dengan beberapa sintaksis yang lebih sederhana jika Anda perlu secara tidak langsung merujuk array:
Untuk kasus penggunaan yang lebih sederhana, saya sarankan sintaks yang dijelaskan dalam Panduan Script Bash Lanjutan .
sumber
foo_1
danfoo_2
bebas dari spasi dan simbol khusus. Contoh untuk entri yang bermasalah:'a b'
akan membuat dua entri di dalamnyamine
.''
tidak akan membuat entri di dalammine
.'*'
akan diperluas ke konten direktori kerja. Anda dapat mencegah masalah ini dengan mengutip:eval 'mine=( "${foo_'"$i"'[@]}" )'
[@]
konstruk."${array[@]}"
akan selalu memperluas ke daftar entri yang benar tanpa masalah seperti pemisahan kata atau perluasan*
. Selain itu, masalah pemisahan kata hanya dapat dielakkan denganIFS
jika Anda mengetahui karakter non-nol yang tidak pernah muncul di dalam array. Lebih jauh lagi, pengobatan harfiah*
tidak dapat dicapai dengan pengaturanIFS
. Entah Anda mengaturIFS='*'
dan membagi bintang-bintang atau Anda menetapkanIFS=somethingOther
dan*
mengembang.|
atauLF
sebagai IFS. Sekali lagi, masalah umum dalam loop adalah bahwa tokenization terjadi secara default sehingga mengutip adalah solusi khusus untuk memungkinkan string tambahan yang berisi token. (Entah itu globbing / ekspansi parameter atau string extended yang dikutip tapi tidak keduanya.) Jika 8 kutipan diperlukan untuk membaca var, shell adalah bahasa yang salah.Untuk array yang diindeks, Anda dapat mereferensikannya seperti:
Array asosiatif dapat dirujuk dengan cara yang sama tetapi perlu
-A
dihidupkandeclare
alih-alih-a
.sumber
Metode tambahan yang tidak bergantung pada versi shell / bash yang Anda miliki adalah dengan menggunakan
envsubst
. Sebagai contoh:sumber
script.sh
mengajukan:Uji:
Sesuai
help eval
:Anda juga dapat menggunakan
${!var}
ekspansi tidak langsung Bash , seperti yang telah disebutkan, namun itu tidak mendukung pengambilan indeks array.Untuk membaca atau contoh lebih lanjut, periksa BashFAQ / 006 tentang Ketidaktahuan .
Namun, Anda harus mempertimbangkan kembali menggunakan tipuan sesuai catatan berikut.
sumber
untuk
varname=$prefix_suffix
format, cukup gunakan:sumber