Подвійна і потрійна заміна в bash і zsh


23

Слідкуйте за основною частиною цього питання .

У bashя можу використовувати ${!FOO}для подвійної заміни, в zsh ${(P)FOO}. В обох працює стара школа (хак-у) eval \$$FOO.

Отже, найрозумнішим і найбільш логічним для мене було б ${${FOO}}, ${${${FOO}}}…подвійне / потрійне / п заміщення. Чому це не працює, як очікувалося?

Друге: Що робить \у evalвисловлюванні? Я вважаю, що це втеча, що робить щось подібне eval \$$$FOOнеможливим. Як зробити потрійну / п заміну на ту, яка працює в кожній оболонці?

Відповіді:


15

\Повинен бути використаний , щоб запобігти розширенню $$(поточний ідентифікатор процесу). Для потрійної заміни вам потрібно подвійне оцінювання, а також більше втеч, щоб уникнути небажаних розширень у кожному овалі:

#! /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

І все-таки: l3=l2; eval eval eval echo \\\$\\$\$$l353294так не зовсім модульно.
Profpatsch

2
Чому я не можу зробити щось на кшталт eval $(eval echo \$$l2)? Я ненавиджу баш, це абсолютно не має сенсу.
Profpatsch

@Profpatsch: Додано більше прикладів.
choroba

Ах, це n². Я навіть не хочу намагатися зрозуміти, чому це так.
Profpatsch

2
@Profpatsch: Легко. На кожному кроці кількість зворотних нахилів на півкільця (так це фактично 2ⁿ).
choroba


6

Припустимо, що значення FOOє дійсним ім'ям змінної (скажімо BAR), eval \$$FOOрозбиває значення BARна окремі слова, розглядає кожне слово як шаблон підстановки та виконує перше слово результату як команду, передаючи інші слова як аргументи. Зворотна косою рисою перед доларом змушує її поводитися буквально, тому аргумент, переданий evalвбудованому, - це чотиризначний рядок $BAR.

${${FOO}}- синтаксична помилка. Це не робить "подвійної підстановки", оскільки немає жодної особливості в жодній із загальних оболонок (як не у цьому синтаксисі). У zsh ${${FOO}} є дійсним і є подвійною підстановкою, але він поводиться інакше, ніж ви хотіли: він виконує два послідовних перетворення на значення FOO, обидва - це перетворення ідентичності, тому це просто фантазійний спосіб написання ${FOO}.

Якщо ви хочете ставитися до значення змінної як до змінної, будьте уважні до того, щоб цитувати речі належним чином. Це набагато простіше, якщо результат встановити змінною:

eval "value=\${$FOO}"

1
Встановивши його як змінну, щоб перше слово не використовувалося як команда? Людина, таку логіку важко зрозуміти. Здається, це загальна проблема, з якою я маю баш.
Profpatsch

4

{ba,z}sh рішення

Ось функція, яка працює в обох {ba,z}sh. Я вважаю, що це також сумісно з POSIX.

Не з глузду з цитуванням, ви можете використовувати його для багатьох рівнів непрямості так:

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

Або якщо у вас більше (!?!) Рівнів непрямості, ви можете використовувати цикл.

Він попереджає, коли дається:

  • Недійсне введення
  • Ім'я змінної, яке не встановлено

# 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?}\""
}

Робоче рішення для мене "оболонки". Сподіваємось, "розширення подвійної змінної, сумісне з POSIX", перенаправляє цю відповідь у майбутньому.
BlueDrink9

2

Так само , як ${$foo}не працює на місці ${(P)foo}в zsh, ні один не чинить ${${$foo}}. Вам просто потрібно вказати кожен рівень непрямості:

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

Звичайно, ${!${!foo}}це не працює bash, оскільки bashне дозволяє вкладені підстановки.


Дякую, це добре знати. Прикро, що це лише zsh, тому ви не можете реально помістити його в сценарій (у всіх є баш, але це не навпаки).
Profpatsch

Я підозрюю, що zshвін встановлюється набагато частіше, ніж можна припустити. Він може бути пов'язаний не так, shяк bashчасто (але не завжди) є, але він все ще може бути доступний для скриптів оболонки. Думайте про це, як ви робите Perl чи Python.
чепнер

2

Навіщо вам це потрібно робити?

Ви завжди можете це зробити в кілька етапів, наприклад:

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

Або скористайтеся такою функцією, як:

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

Потім:

$ 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, в zsh:

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

Гм, скориставшись декількома кроками, мені досі не приходило… дивно.
Profpatsch

З моєї точки зору, очевидно, чому @Profpatsch хоче це зробити . Тому що це найінтуїтивніший спосіб зробити це. Бути важко здійснити кілька підстановок в баші - інша річ.
Нікос Олександріс

2

POSIX рішення

Не з глузду з цитуванням, ви можете використовувати цю функцію для багатьох рівнів непрямості так:

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

Або якщо у вас більше (!?!) Рівнів непрямості, ви можете використовувати цикл.

Він попереджає, коли дається:

  • Недійсне введення
  • Більше одного аргументу
  • Ім'я змінної, яке не встановлено

# 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?}\""
}

Чи є причина, що ви не поєднали цю відповідь з попередньою? На перший погляд я не бачу різниці між ними.
BlueDrink9
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.