0

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

zsh 5.7.1 (x86_64-apple-darwin19.0)

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

我正在 Mac 上开发一个 shell 脚本,我试图让它在 bash 和 zsh 之间保持可移植性,所以数组索引是一个考虑因素。我知道我可以将 KSH_ARRAYS 设置为从 0 开始索引,但我决定在操作系统中查询正在使用的 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

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

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

4

2 回答 2

1

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

在给定的场景中,间接扩展不会提供价值,因为可以很容易地直接分配值。无论 shell 之间的间接扩展使用不同的语法,它们都必须以这种方式分配。事实上,shell 之间的其他语法差异使得检测 shell 的整个前提毫无意义!然而,抛开这一点不谈,行为上的差异是基于脚本是“执行”还是“来源”而调用不同 shell 的结果。采购的行为在网上有大量的解释,有很好的记录,但这里是它的工作原理:

执行脚本 使用“./”语法来执行脚本。以这种方式运行时,脚本在子 shell 中执行。脚本对其外壳的任何更改都将应用于子外壳,而不是启动脚本的外壳,因此当外壳退出时,这些更改将丢失,因为执行它的子外壳也被破坏。例如,如果脚本更改了工作目录,它会在子 shell 中这样做。脚本终止时,启动脚本的主 shell 的工作目录保持不变。如果您想对启动脚本的 shell 进行更改,则它必须是 source 的。

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

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

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

“${!A_VAR}"

正确的语法是:

"${(P)A_VAR}"

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

于 2020-06-20T17:30:53.393 回答
1

为了增加您的答案(我要说的评论太长了),我想不出任何应用程序,为什么如果没有来源您的脚本可能会有用。实际上,我恰好在一次遇到了需要这样一个脚本的情况:

由于我不仅使用 zsh 作为交互式 shell,有时还使用 bash,所以我编写了我的 .zshrc 和 .bashrc 来设置所有内容(包括定义变量和用于交互使用的 shell 函数)。为了安全工作,我尝试将同时在 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 运行,反之亦然,所以我不需要查询我拥有什么样的 shell。

于 2020-06-21T09:09:41.427 回答