脚本在执行时运行,但在获取源脚本时失败

时间:2020-06-19 03:03:41

标签: bash zsh parameter-expansion

原始标题:脚本来源(zsh)时,间接参数替换会中断

zsh 5.7.1(x86_64-apple-darwin19.0)

GNU bash,版本4.4.20(1)-发行版(x86_64-pc-linux-gnu)

我正在Mac上开发shell脚本,并试图使其在bash和zsh之间可移植,因此需要考虑数组索引。我知道我可以设置KSH_ARRAYS来使索引从0开始,但是我决定在OS上查询正在使用的Shell,并相应地设置开始索引,这导致了以下问题。

使用间接扩展是有意义的(无论如何对我来说!),这就是导致问题的原因。考虑脚本indirect.sh:

#! /bin/bash

declare -r ARRAY_START_BASH=0
declare -r ARRAY_START_ZSH=1

declare -r SHELL_BASH=0
declare -r SHELL_ZSH=1

# Indirect expansion is used to reference the values of the variables declared
# in this case statement e.g. ${!ARRAY_START}
case $(basename $SHELL) in
  "bash" )
    declare -r SHELL_ID=SHELL_BASH
    declare -r ARRAY_START=ARRAY_START_BASH
    ;;

  "zsh" )
    declare -r SHELL_ID=SHELL_ZSH
    declare -r ARRAY_START=ARRAY_START_ZSH
    ;;

  * )
    return 1
    ;;

esac

echo "Shell ID: ${!SHELL_ID} Index arrays from: ${!ARRAY_START}"

在同一目录中从命令行运行时,效果很好:

<my home> ~ % echo "$(./indirect.sh)"
Shell ID: 1 Index arrays from: 1

获取脚本时出现问题:

<my home> ~ % echo "$(. ~/indirect.sh)"
/Users/<me>/indirect.sh:28: bad substitution

我不明白为什么采购脚本会更改参数扩展的行为。

这是预期的行为吗?如果是这样,我将不胜感激,如果有人可以解释一下,并希望能提供解决方法。

2 个答案:

答案 0 :(得分:1)

原始帖子中描述的问题与间接扩展无关。行为上的差异是根据脚本是“执行”还是“源代码”调用了不同的shell的结果。这些差异揭示了从$ SHELL变量派生shell的基本缺陷,该变量支撑了脚本的设计。如果$ SHELL中定义的shell与shebang不匹配,则脚本在源代码或执行时都会失败。以下是解释。

在给定的情况下,间接扩展无法提供价值,因为可以轻松地直接分配价值。无论给外壳之间的间接扩展使用不同的语法,都必须以这种方式分配它们。实际上,外壳程序之间的其他语法差异使检测外壳程序的整个前提变得毫无意义!但是,撇开这些,行为上的差异是由于根据脚本是“执行”还是“源代码”调用了不同的shell。网上有大量说明,很好地记录了采购行为,但在上下文中,它是如何工作的:

执行脚本 使用“ ./”语法执行脚本。 以这种方式运行时,脚本在子外壳中执行。任何变化 脚本将其外壳程序应用于子外壳程序,而不是外壳程序 在其中启动了脚本,因此当 shell退出是因为执行它的子shell被破坏为 好。例如,如果脚本更改了工作目录,则它将 在子外壳中执行此操作。主shell的工作目录 脚本终止时启动的脚本未更改。如果你 要更改启动脚本的外壳,它 必须是来源。

采购脚本 使用“ source”语法来获取 脚本。当以这种方式运行时,脚本实际上成为参数 用于源命令,该命令处理适当的调用 执行。某些shell(例如ksh)使用单个句点“。”代替 “来源”。

使用“ ./”语法执行脚本时,文件顶部的shebang用于确定使用哪个shell。当获取脚本时,将忽略shebang,而使用启动脚本的shell。另外请注意,用于执行脚本的“ ./”命令语法中出现的时间段与偶尔用作源命令别名的时间段无关。

帖子中的脚本在shebang语句中使用了bash,因此该脚本在执行时就可以使用,因为它是使用bash运行的。当它来自zsh时,会遇到不正确的间接扩展语法:

“${!A_VAR}"

正确的语法是:

"${(P)A_VAR}"

但是,更正语法无济于事,因为它在执行时会失败。 shebang将调用bash,语法再次错误。这使得间接访问对访问旨在指示外壳正在使用的变量无用。更重要的是,基于外壳的环境查询的设计存在缺陷,这是因为最终使用的外壳有所不同,具体取决于执行脚本还是执行脚本。

答案 1 :(得分:1)

要添加到您的答案中(我要说的评论太长了),我想不出任何应用程序,为什么在来源的情况下,您的脚本会很有用。实际上,我碰巧遇到过这样一种脚本的需要:

由于我不仅将zsh用作交互式外壳,而且有时将bash用作交互式外壳,所以我编写了.zshrc和.bashrc来设置所有内容(包括定义变量和外壳函数以供交互式使用)。为了安全工作, 我尝试将在bash和zsh下都可以使用的代码放入一个文件中(例如:.commonrc),而我的.zshrc和.bashrc里面有一个

source .commonrc

虽然bash和zsh有很多不同之处,但只要做一些调整,我就不能将它们放入.commonrc。头疼的原因之一显然是数组的不同索引,您似乎试图解决了这一问题。所以我也有类似的功能。但是,我不需要为此构造case。相反,我的.bashrc看起来像这样(使用您对变量的命名):

...
declare -r ARRAY_START=0
source .commonrc
...

我的.zshrc看起来像这样:

...
declare -r ARRAY_START=1
source .commonrc
...

由于.bashrc不会从zsh运行,反之亦然,因此我不需要查询我拥有哪种外壳。