$ {! FOO} и zsh

13

${!FOO}выполняет двойную подстановку bash, то есть принимает (строковое) значение FOO и использует его в качестве имени переменной.
zshне поддерживает эту функцию.

Есть ли способ сделать эту работу такой же в bashи zsh?

Фон:

У меня есть список переменных среды, как

PATH MAIL EDITOR

и хотите сначала вывести имена переменных, а затем их значения.

Это работает, bashно не zsh:

for VAR in LIST
do
        echo $VAR
        echo ${!VAR}
done

Это должно быть как-то возможно по-старому eval, но я не могу заставить его работать:

for VAR in LIST
do
        echo $VAR
        echo `eval \$$VAR`
done

Я никогда не пойму, почему я не могу просто сделать произвольные глубокие замены, такие как ${${VAR}}или даже ${${${VAR}}}если это необходимо, поэтому объяснение этого было бы тоже неплохо.

Profpatsch
источник
1
ХА! Я думал, что Zsh должен был иметь больше возможностей.
Ярослав Рахматуллин
4
Не нужно хвастаться bash, он действительно имеет ту же функцию (и намного больше), использует только другой шаблон, а именно ${(p)FOO}.
Profpatsch
2
@ ЯрославРахматуллин, (e)флаг расширения параметров zsh был добавлен в zsh-2.6-beta17 (май 1996 г.), (P)флаг в 3.1.5-pws-7 (1999 г.). Баш ${!var}в 2.0 (декабрь 1996).
Стефан Шазелас

Ответы:

18

У bash и zsh есть способ выполнить косвенное расширение, но они используют другой синтаксис.

Достаточно легко выполнить косвенное расширение, используя eval; это работает во всех POSIX и большинстве оболочек Bourne. Будьте внимательны, чтобы правильно заключить в кавычки, если значение содержит символы, которые имеют особое значение в оболочке.

eval "value=\"\${$VAR}\""
echo "$VAR"
echo "$value"

${${VAR}}не работает, потому что это не функция, которую реализует любая оболочка. Вещи внутри фигурных скобок должны соответствовать синтаксическим правилам, которые не включают ${VAR}. (В zsh это поддерживаемый синтаксис, но он делает что-то иное: вложенные подстановки выполняют последовательные преобразования для одного и того же значения; ${${VAR}}эквивалентно, $VARтак как это выполняет преобразование идентичности дважды для значения.)

Жиль "ТАК - прекрати быть злым"
источник
19

Комментарий под первоначальным вопросом от Profpatsch приводит пример ${(p)FOO}вывода содержимого ссылки на косвенную переменную. Флаг неправильный или опечатка, это должен быть заглавная буква P, а не строчная буква p. Использование: ${(P)FOO}.

Следующее должно дать желаемый результат:

#! /bin/zsh
LIST=(PATH MAIL EDITOR)
for VAR in ${LIST}
do
    print "${VAR}:  ${(P)VAR}"
done

Со страницы руководства zshexpn ; раздел - Флаги расширения параметров :

п

  This forces the value of the parameter name to be interpreted as a further
  parameter name,  whose  value  will  be used where appropriate.  Note that
  flags set with one of the typeset family of commands (in particular case
  transformations) are not applied to the value of name used in this fashion.

  If used with a nested parameter or command substitution, the result of that
  will be taken as a parameter  name  in the  same  way.   For  example,  if
  you  have  `foo=bar'  and `bar=baz', the strings ${(P)foo}, ${(P)${foo}}, and
  ${(P)$(echo bar)} will be expanded to `baz'

Одно время я читал, почему ${${${VAR}}}не дает ожидаемого результата, но в настоящее время я не могу его найти. Вы можете сделать что-то вроде следующего:

first="second" ; second="third" ; third="fourth" ; fourth="fifth"
print ${(P)${(P)${(P)first}}}
fifth
Friartek
источник
3

Вы не используете eval правильно. В вашем примере значение $ VAR, которому предшествует «$» (т. Е. «$ VALUE»), будет выполнено как команда. Это не то, что вы хотите. Вы хотите оценить расширение переменной, имя которой взято из другой переменной.

$ for i in `echo PATH MAIL EDITOR`; 
    do eval moo="\${$i}" 
    echo $moo 
done
/usr/local/sbin:/usr/local/bin:/usr/sbin:/u (...)
/var/mail/root
nano
Ярослав Рахматуллин
источник
0

{ba,z}sh решение

Вот функция, которая работает в обоих {ba, z} sh. Я считаю, что это также POSIX-совместимый.

Предупреждает когда дано:

  • Нулевой ввод
  • Более одного аргумента
  • Имя переменной, которая не установлена

# 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 [ "$#" -ne 1 ] || [ -z "${1-}" ]; then
    printf 'var_expand: expected one non-empty argument\n' >&2;
    return 1;
  fi
  eval printf '%s' "\"\${$1?}\""
}
Том Хейл
источник
Там нет functionни [[...]]в POSIX sh.
Стефан Шазелас
Проверка на отсутствие аргумента должна быть с [ "$#" -eq 0 ](или, вероятно, так [ "$#" -ne 1 ]как вы ожидаете только один аргумент).
Стефан Шазелас
@ StéphaneChazelas Спасибо, обновлено. Существует ли эталонная оболочка для соответствия POSIX? Я думаю, что shell-checkможет помочь тоже.
Том Хейл,
@ StéphaneChazelas Повторяю тест, я также хочу потерпеть неудачу, когда дан единственный нулевой аргумент. Я добавил ваше второе предложение. Ура!
Том Хейл,
Я согласен с ${1-}приведенной цитатой a='"" -o -n 1' [ -z $a ]== 0. Но не $#всегда ли будет один токен?
Том Хейл,