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?

Profpatsch
sumber

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
choroba
sumber
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
10
#!/bin/bash

hello=world
echo=hello

echo $echo ${!echo}
Christopher Abad
sumber
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}"
Gilles 'SANGAT berhenti menjadi jahat'
sumber
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?}\""
}
Tom Hale
sumber
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.

chepner
sumber
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
Stéphane Chazelas
sumber
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?}\""
}
Tom Hale
sumber
Apakah ada alasan Anda belum menggabungkan jawaban ini dengan yang sebelumnya? Sepintas saya tidak melihat perbedaan di antara mereka.
BlueDrink9