Двойная и тройная замена в bash и zsh

23

Продолжение к фоновой части в этом вопросе .

В bashя могу использовать ${!FOO}для двойной замены, в zsh ${(P)FOO}. В обоих случаях работает старая школа (hack-y) eval \$$FOO.

Итак, самая разумная и логичная вещь для меня - ${${FOO}}, ${${${FOO}}}…двойная / тройная / n замена. Почему это не работает, как ожидалось?

Второе: что делает \в evalзаявлении? Я считаю, что это спасение, что-то вроде eval \$$$FOOневозможного. Как сделать тройную / n замену тем, что работает в каждой оболочке?

Profpatsch
источник

Ответы:

15

\Должен быть использован , чтобы предотвратить расширение $$(текущий идентификатор процесса). Для тройной подстановки вам понадобится двойное вычисление eval, а также дополнительные экранирования, чтобы избежать нежелательных расширений в каждом 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
источник
Еще: l3=l2; eval eval eval echo \\\$\\$\$$l353294так что не совсем модульный.
Profpatsch
2
Почему я не могу сделать что-то подобное eval $(eval echo \$$l2)? Я ненавижу Баш, это не имеет абсолютно никакого смысла.
Profpatsch
@Profpatsch: добавлено больше примеров.
Чороба
Ах, это n². Я даже не хочу пытаться понять, почему это так.
Profpatsch
2
@Profpatsch: Легко. На каждом шаге число обратных косых черт уменьшается наполовину (на самом деле это 2ⁿ).
Чороба
10
#!/bin/bash

hello=world
echo=hello

echo $echo ${!echo}
Кристофер Абад
источник
6

Предположим, что значение FOOявляется допустимым именем переменной (скажем BAR), eval \$$FOOразбивает значение BARна отдельные слова, обрабатывает каждое слово как шаблон подстановочного знака и выполняет первое слово результата как команду, передавая другие слова в качестве аргументов. Обратная косая черта перед долларом делает его трактуемым буквально, поэтому аргумент, передаваемый evalвстроенному, представляет собой строку из четырех символов $BAR.

${${FOO}}это синтаксическая ошибка Он не выполняет «двойную замену», потому что такой функции нет ни в одной из общих оболочек (в любом случае, с этим синтаксисом). В zsh ${${FOO}} он действителен и представляет собой двойную подстановку, но он ведет себя не так, как вам бы хотелось: он выполняет два последовательных преобразования значения FOO, оба из которых являются преобразованием идентификаторов, так что это просто причудливый способ записи ${FOO}.

Если вы хотите трактовать значение переменной как переменную, будьте осторожны с правильным заключением в кавычки. Намного проще, если вы установите результат в переменную:

eval "value=\${$FOO}"
Жиль "ТАК - перестань быть злым"
источник
1
Задать его как переменную, чтобы первое слово не использовалось в качестве команды? Человек, такую ​​логику трудно понять. Это общая проблема, которую я, похоже, имею с bash.
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что не допускает вложенные замены.

chepner
источник
Спасибо, это приятно знать. Жаль, что это только zsh, так что вы не можете поместить это в скрипт (у всех есть bash, но это не наоборот).
Profpatsch
Я подозреваю, zshчто устанавливается гораздо чаще, чем вы можете предположить. Возможно, он не связан с тем, shкак bashчасто (но не всегда), но он все еще может быть доступен для сценариев оболочки. Думайте об этом как о Perl или Python.
chepner
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