【问题标题】:Script runs when executed but fails when sourced脚本在执行时运行,但在获取时失败
【发布时间】:2020-06-19 03:03:41
【问题描述】:

原标题: 脚本来源时间接参数替换中断(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

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

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

【问题讨论】:

  • 如果你从命令行输入脚本名来运行它,它是由 bash 执行的,但是当你从你的 zsh 中获取它时,它是由 zsh 执行的。通常,您不会期望程序在解释为不同语言时产生相同的结果。另请注意,查询 SHELL 的值并不能可靠地告诉您正在运行的 shell。例如,当我在 Mac 上运行 zsh 时,SHELL 设置为 bash。无论如何,在你的脚本中添加一个set -x,这样你就可以看到出错的地方和原因。
  • zsh 使用与 bash 不同的语法进行间接参数扩展。:${(P)SHELL_ID}
  • 执行脚本时使用shebang。当你使用. 时,使用当前的shell (zsh),忽略shebang。
  • 您有两个问题:1) 通常,您无法编写在bashzsh 中同样工作的脚本,除非您坚持符合POSIX 的功能。 2) 一般来说,您不应该尝试编写可以执行或获取的脚本。您通常会获取一个文件,因为它需要影响调用者的环境,而执行脚本不能这样做。
  • @chepner:当然,shebang。如果不是一直浪费时间,那会很有趣!文档中指出了用于间接扩展的 zsh 语法。以为我看过那里,但显然我没有。当然,问题更为根本。我不太确定发布答案的协议,所以我自己回答了。如果有错误请告诉我,我会编辑。

标签: bash zsh parameter-expansion


【解决方案1】:

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

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

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

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

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

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

“${!A_VAR}"

正确的语法是:

"${(P)A_VAR}"

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

【讨论】:

    【解决方案2】:

    要添加到您的答案(我要说的评论太长了),我想不出任何应用程序,为什么如果没有来源您的脚本会很有用。实际上,我自己在一个场合就遇到了这样一个脚本的需求:

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

    source .commonrc
    

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

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

    我的 .zshrc 看起来像这样:

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

    由于 .bashrc 不会从 zsh 运行,反之亦然,所以我不需要查询我拥有什么样的 shell。

    【讨论】:

      猜你喜欢
      • 2015-04-06
      • 2021-03-12
      • 2022-09-30
      • 1970-01-01
      • 1970-01-01
      • 2013-10-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多