Substitusi ganda dan tiga dalam bash dan zsh


23

Tindak lanjuti bagian latar belakang dalam pertanyaan ini .

Di bashsaya bisa gunakan ${!FOO}untuk substitusi ganda, di zsh ${(P)FOO}. Dalam keduanya, old-school (hack-y) eval \$$FOOberfungsi.

Jadi, hal yang paling cerdas dan paling logis bagi saya adalah ${${FOO}}, ${${${FOO}}}…substitusi ganda / tripel / n. Mengapa ini tidak bekerja seperti yang diharapkan?

Kedua: Apa yang \dilakukan dalam evalpernyataan itu? Saya pikir itu pelarian, membuat sesuatu yang eval \$$$FOOmustahil. Bagaimana melakukan substitusi tripel / n dengan yang bekerja di setiap shell?

Jawaban:


15

The \harus digunakan untuk mencegah perluasan $$(proses saat id). Untuk substitusi tiga kali lipat, Anda perlu eval ganda, jadi lebih banyak lolos untuk menghindari ekspansi yang tidak diinginkan di setiap eval:

#! /bin/bash
l0=value
l1=l0
l2=l1
l3=l2
l4=l3

echo $l0
eval echo \$$l1
eval eval echo \\$\$$l2
eval eval eval echo \\\\$\\$\$$l3
eval eval eval eval echo \\\\\\\\$\\\\$\\$\$$l4

Namun: l3=l2; eval eval eval echo \\\$\\$\$$l353294jadi tidak persis modular.
Profpatsch

2
Kenapa aku tidak bisa melakukan sesuatu seperti itu eval $(eval echo \$$l2)? Aku benci pesta, itu sama sekali tidak masuk akal.
Profpatsch

@Profpatsch: Menambahkan lebih banyak contoh.
choroba

Ah, ini n². Saya bahkan tidak ingin mencoba mengapa ini seperti ini.
Profpatsch

2
@ Profilpatsch: Mudah. Dalam setiap langkah, jumlah backslash terbagi dua (jadi 2ⁿ, sebenarnya).
choroba


6

Misalkan nilai FOOadalah nama variabel yang valid (katakanlah BAR), pisahkan eval \$$FOOnilai BARmenjadi kata-kata yang terpisah, perlakukan setiap kata sebagai pola wildcard, dan jalankan kata pertama dari hasil sebagai perintah, dengan kata lain sebagai argumen. Garis miring terbalik di depan dolar membuatnya diperlakukan secara harfiah, sehingga argumen yang diteruskan ke evalbuiltin adalah string empat karakter $BAR.

${${FOO}}adalah kesalahan sintaksis. Itu tidak melakukan "substitusi ganda" karena tidak ada fitur seperti itu di shell umum (tidak dengan sintaks ini pula). Dalam zsh, ${${FOO}} adalah sah dan merupakan substitusi ganda, tetapi berperilaku berbeda dari apa yang Anda ingin: ia melakukan dua transformasi berturut-turut pada nilai FOO, yang keduanya transformasi identitas, sehingga hanya cara mewah untuk menulis ${FOO}.

Jika Anda ingin memperlakukan nilai suatu variabel sebagai variabel, berhati-hatilah untuk mengutip sesuatu dengan benar. Jauh lebih mudah jika Anda mengatur hasilnya ke variabel:

eval "value=\${$FOO}"

1
Mengaturnya sebagai variabel, sehingga kata pertama tidak digunakan sebagai perintah? Sobat, logika seperti ini sulit untuk dipahami. Ini masalah umum yang tampaknya saya miliki dengan bash.
Profpatsch

4

{ba,z}sh larutan

Inilah fungsi yang bekerja di keduanya {ba,z}sh. Saya percaya itu juga sesuai dengan POSIX.

Tanpa tergila-gila dengan mengutip, Anda dapat menggunakannya untuk banyak tingkat tipuan seperti:

$ a=b
$ b=c
$ c=d
$ echo $(var_expand $(var_expand $a)
d

Atau jika Anda memiliki lebih banyak tingkat tipu daya (!?!) Anda bisa menggunakan loop.

Ini memperingatkan ketika diberikan:

  • Masukan kosong
  • Nama variabel yang tidak disetel

# Expand the variable named by $1 into its value. Works in both {ba,z}sh
# eg: a=HOME $(var_expand $a) == /home/me
var_expand() {
  if [ -z "${1-}" ] || [ $# -ne 1 ]; then
    printf 'var_expand: expected one argument\n' >&2;
    return 1;
  fi
  eval printf '%s' "\"\${$1?}\""
}

Solusi lintas-shell yang berfungsi untuk saya. Semoga "ekspansi variabel ganda yang kompatibel dengan POSIX" mengalihkan ke jawaban ini di masa mendatang.
BlueDrink9

2

Sama seperti ${$foo}tidak bekerja di tempat ${(P)foo}di zsh, juga tidak ${${$foo}}. Anda hanya perlu menentukan setiap tingkat tipuan:

$ foo=bar
$ bar=baz
$ baz=3
$ echo $foo
bar
$ echo ${(P)foo}
baz
$ echo ${(P)${(P)foo}}
3

Tentu saja, ${!${!foo}}tidak berfungsi bash, karena bashtidak memungkinkan penggantian tersarang.


Terima kasih, itu baik untuk diketahui. Sayang sekali ini hanya zsh, jadi Anda tidak dapat benar-benar memasukkannya ke dalam skrip (semua orang memiliki bash, tetapi ini bukan sebaliknya).
Profpatsch

Saya menduga zshdiinstal jauh lebih sering daripada yang Anda duga. Ini mungkin tidak terhubung dengan shsuka bashsering (tapi tidak selalu), tetapi mungkin masih tersedia untuk skrip shell. Anggap saja seperti yang Anda lakukan Perl atau Python.
chepner

2

Mengapa Anda perlu melakukan itu?

Anda selalu dapat melakukannya dalam beberapa langkah seperti:

eval "l1=\${$var}"
eval "l2=\${$l1}"
...

Atau gunakan fungsi seperti:

deref() {
  if [ "$1" -le 0 ]; then
    eval "$3=\$2"
  else
    eval "deref $(($1 - 1)) \"\${$2}\" \"\$3\""
  fi
}

Kemudian:

$ a=b b=c c=d d=e e=blah
$ deref 3 a res; echo "$res"
d
$ deref 5 a res; echo "$res"
blah

FWIW, di zsh:

$ echo ${(P)${(P)${(P)${(P)a}}}}
blah

Huh, menggunakan beberapa langkah tidak terpikir olehku sampai sekarang ... aneh.
Profpatsch

Sudah jelas, dari sudut pandang saya, mengapa @Profpatsch ingin melakukan itu . Karena itu adalah cara paling intuitif untuk melakukannya. Menjadi sulit untuk menerapkan beberapa substitusi di bash adalah hal lain.
Nikos Alexandris

2

Solusi POSIX

Tanpa tergila-gila dengan mengutip, Anda dapat menggunakan fungsi ini untuk banyak tingkat tipuan seperti:

$ a=b
$ b=c
$ c=d
$ echo $(var_expand $(var_expand $a)
d

Atau jika Anda memiliki lebih banyak tingkat tipu daya (!?!) Anda bisa menggunakan loop.

Ini memperingatkan ketika diberikan:

  • Masukan kosong
  • Lebih dari satu argumen
  • Nama variabel yang tidak disetel

# Expand the variable named by $1 into its value. Works in both {ba,z}sh
# eg: a=HOME $(var_expand $a) == /home/me
function var_expand {
  if [ "$#" -ne 1 ] || [ -z "${1-}" ]; then
    printf 'var_expand: expected one argument\n' >&2;
    return 1;
  fi
  eval printf '%s' "\"\${$1?}\""
}

Apakah ada alasan Anda belum menggabungkan jawaban ini dengan yang sebelumnya? Sepintas saya tidak melihat perbedaan di antara mereka.
BlueDrink9
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.