10

是否可以从函数中的单个语句声明和导出数组?

我目前的解决方法是先声明,然后再导出。

f() { foo=(1 2 3); export foo; }; f; export -p | grep foo=
declare -ax foo='([0]="1" [1]="2" [2]="3")'

我观察到:

f() { export bar=(1 2 3); }; f; export -p | grep bar=
<no output>

和:

f() { export baz="(1 2 3)"; }; f; export -p | grep baz=
declare -x baz="(1 2 3)" # not an array

我使用 v3.2.48(1)-release 并且无法升级。


一些背景:

我有一个朋友,我想和他一起学习“一些”Django。

他在命令行上比我更无能,在 OSX hackintosh 上需要以下内容:

  • 启动交互式外壳
  • 根据我的规范,找到包括 django bin 目录的 PATH 变量
  • 找到一个更新的 PYTHONPATH env var,各种 django 库可见
  • 一个不错的交互式 ipython shell,双击后开始输入命令
  • (棘手的)一旦他 CTRL-D 从 ipython 退出时回退到一个交互式 shell

在 Windows 上,我会为命令快捷方式(如 cmd.exe)设置别名,设置自定义环境变量并启动 ipython。这有效:退出 ipython 后,仍然会发现自己处于命令解释器中。

这对 OSX 的标准 bash 有可能吗?我玩过 bash -c 但很多事情都不起作用,比如切换到目录,能够退出 python 并在终端等。也玩过 -s。

4

3 回答 3

9

您不能在 Bash(或任何其他 shell)中导出数组。Bash永远不会将数组导出到环境中(直到有一天可能实现,请参阅bugs手册页中的部分)。

这里发生了几件事。同样,-x在数组上设置属性是无意义的,因为它实际上不会被导出。您看到这些结果的唯一原因是您在本地化之前定义了数组,导致它下降到该名称已成为本地(或全局范围)的下一个最外层范围。

所以要明确一点,每当您使用声明命令时,您总是在创建一个局部变量,除非您使用exportreadonly使用非数组赋值作为参数,因为这些是 POSIX,它没有指定局部变量或数组。

function f {
    typeset -a a # "a" is now local to "f"
    g
    printf 'Now back in "f": %s\n' "$(typeset -p a)"
}

function g {
    a=(1 2 3)                               # Assigning f's localized "a"
    typeset -a a                            # A new local a
    printf 'In "g": %s\n' "$(typeset -p a)" # g's local is now empty.
    a=(a b c)                               # Now set g's local to a new value.
    printf 'Still in "g": %s\n' "$(typeset -p a)"
}

f

# In "g": declare -a a='()'
# Still in "g": declare -a a='([0]="a" [1]="b" [2]="c")'
# Now back in "f": declare -a a='([0]="1" [1]="2" [2]="3")'

似乎如果你给一个数组作为参数,export那么 Bash 就会像任何其他声明命令一样使它成为本地的。基本上不export用于定义数组。export是一个 POSIX 内置函数,它的行为对于数组是未定义的,这可能就是为什么 bash 只是把它当作你使用过typeset -ax a=(1 2 3). 使用typesetlocaldeclare。真的没有办法知道什么是“正确的”,甚至无法与其他 shell 进行比较。ksh93 是唯一一个接受数组赋值作为声明命令的参数的另一个,它的行为与 Bash 不一致。

要理解的重要一点是环境与它无关,当您尝试使用仅限 POSIX 的命令执行非标准操作时,您只是在玩弄当地人的怪癖。

为了真正得到你想要的效果,你可以使用typeset -p.

function f {
    typeset -a "${1}=(1 2 3)"
    typeset -p "$1"
}

typeset -a arr
eval "$(f arr)"
typeset -p arr

Bash 保证你会得到正确的结果,但我发现这不是很有用并且很少使用这种方法。通常最好让调用者定义一个本地并使用动态范围来完成工作(正如您已经发现的那样......只需在不导出的情况下进行)。

在 Bash 4.3 中,您可以使用typeset -n,这确实是处理此问题的最正确方法。

于 2013-04-06T20:00:38.450 回答
3

第一个选项似乎是唯一可行的选项。我尝试了bash4.2 和 3.2.48。对我来说,有趣的信息是您的示例的这些次要变体:

$ f() { declare -a bar=(1 2 3); export bar; export -p | grep bar=; }; f; export -p | grep bar=
declare -ax bar='([0]="1" [1]="2" [2]="3")'
$ f() { export  bar=(1 2 3); export -p | grep bar=; }; f; export -p | grep bar=
declare -ax bar='([0]="1" [1]="2" [2]="3")'
$ f() { bar=(1 2 3); export bar; export -p | grep bar=; }; f; export -p | grep bar=
declare -ax bar='([0]="1" [1]="2" [2]="3")'
declare -ax bar='([0]="1" [1]="2" [2]="3")'
$ unset bar
$ f() { bar=(1 2 3); }; f; set | grep bar=
bar=([0]="1" [1]="2" [2]="3")
    bar=(1 2 3)
$

在这些示例中,我测试了export函数内部以及函数外部。因为变量是在函数中定义的,所以它们似乎仅限于函数的范围。例外情况是在应用任何属性之前定义变量 - 最后两个函数。创建一个全局变量,然后导出(在一种情况下)。

因此,如果您要从函数中导出数组,则必须在不使用declareorexport语句的情况下创建它(因为这些语句使变量成为函数的本地变量),然后将其导出。

我希望能解释它;我可以模糊地看到发生了什么,这是有道理的,在时尚之后。我不确定我是否解释了它以及应该解释它。


在手册的declare部分中bash,它说:

在函数中使用时,declare使每个名称都成为本地名称,与local命令一样。

中没有等效的措辞export。但是,观察到的行为好像 export是由 实现的declare -x

于 2013-04-05T04:46:11.130 回答
0

如果我想导出数组 MY_ARRAY,我使用:

[[ $MY_ARRAY ]] && export A_MY_ARRAY=$(declare -p MY_ARRAY)

在呼叫方和

[[ $A_MY_ARRAY =~ ^declare ]] && eval $A_MY_ARRAY

在子脚本。

于 2017-10-16T11:15:52.347 回答