bash和zsh中的两次和三次替换


23

跟踪该问题的背景部分。

在中,bash我可以使用${!FOO}双重替换zsh ${(P)FOO}。在这两种方法中,老式的(hack-y)都eval \$$FOO可以。

因此,对我来说,最聪明,最合乎逻辑的事情是${${FOO}}, ${${${FOO}}}…使用double / triple / n替换。为什么这不能按预期工作?

二:什么是\在做eval陈述?我认为这是一种逃避,使eval \$$$FOO不可能的事情变成现实。如何用在每个shell中都可以使用的三/ n替换?

Answers:


15

\必须用于防止的膨胀$$(当前进程id)。对于三重替换,您需要双重评估,因此还需要进行更多的转义,以避免每个评估中不必要的扩展:

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

2
为什么我不能做类似的事情eval $(eval echo \$$l2)?我讨厌bash,这绝对没有道理。
Profpatsch 2013年

@Profpatsch:添加了更多示例。
choroba

啊,是n²。我什至不想试图弄明白为什么会这样。
Profpatsch 2013年

2
@Profpatsch:很简单。在每个步骤中,反斜杠的数量减半(实际上是2ⁿ)。
choroba


6

假设的值FOO是有效的变量名(例如BAR),eval \$$FOO将的值拆分为BAR多个单独的单词,将每个单词视为通配符模式,并将结果的第一个单词作为命令执行,将其他单词作为参数传递。美元前面的反斜杠使它按字面意义处理,因此传递给eval内置函数的参数是四字符字符串$BAR

${${FOO}}是语法错误。它不会执行“双重替换”,因为在任何常见的shell中都没有这样的功能(无论如何都没有这种语法)。在zsh中,它${${FOO}} 有效的并且是双重替换,但其行为与您想要的不同:它对的值执行两个连续的转换FOO,这两个转换都是身份转换,因此这只是一种奇特的编写方式${FOO}

如果要将变量的值视为变量,请注意正确引用它们。如果将结果设置为变量,则容易得多:

eval "value=\${$FOO}"

1
将其设置为变量,以便第一个单词不用作命令?伙计,这种逻辑很难掌握。这似乎是bash遇到的普遍问题。
Profpatsch 2013年

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}in 工作zsh一样${${$foo}}。您只需要指定每个间接级别:

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

当然,${!${!foo}}在中不起作用bash,因为bash不允许嵌套替换。


谢谢,很高兴知道。遗憾的是,这仅适用于zsh,因此您不能真正将其放在脚本中(每个人都有bash,但并非相反)。
Profpatsch

我怀疑zsh安装次数比您想象的要多。它可能无法联系到sh喜欢bash经常(但不总是)是,但它仍可能适用于shell脚本。就像使用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 2013年

从我的角度来看,很明显@Profpatsch为什么要这样做。因为这是最直观的方法。作为难以实现在bash多重替换是另一回事。
Nikos Alexandris

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
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.