【问题标题】:Converting a BASH script to run on SH (via BusyBox)将 BASH 脚本转换为在 SH 上运行(通过 BusyBox)
【发布时间】:2020-10-14 07:40:08
【问题描述】:

我有一台华硕路由器,运行最新版本的 FreshTomato - BusyBox 附带。

我需要运行一个考虑到 BASH 的脚本 - 它是 this script 的改编版 - 但它无法运行并出现以下错误: line 41: syntax error: bad substitution

使用shellcheck.net 检查脚本会产生以下错误:

Line 41:
        for optionvarname in ${!foreign_option_*} ; do
                             ^-- SC3053: In POSIX sh, indirect expansion is undefined.
                             ^-- SC3056: In POSIX sh, name matching prefixes are undefined.
 
Line 42:
        option="${!optionvarname}"
                ^-- SC3053: In POSIX sh, indirect expansion is undefined.

这些是导致问题的行:

for optionvarname in ${!foreign_option_*} ; do   # line 41
    option="${!optionvarname}"                   # line 42

    # do some stuff with $option...
done

如果我的理解是正确的,那么原始脚本只是对名称以foreign_option_ 开头的所有变量执行一些操作

但是,据我所知,${!foreign_option_*}${!optionvarname} 构造都是 BASH 特定的,并且不符合 POSIX,因此不可能进行直接的“bash 到 sh”代码转换。

我尝试创建一个指向busybox/bin/bash 符号链接,但出现Read-only file system 错误。

那么,我怎样才能让这个脚本在我的路由器上运行呢?我只看到两个选项,但我不知道如何实现:

  1. 让 BusyBox 将脚本解释为 BASH 而不是 SH - 我可以为此使用特定的 shebang 吗?
    • 似乎是最快的选择,但前提是 BusyBox 具有 BASH 的“完整”实现
  2. 更改脚本代码以不使用 BASH 细节。
    • 这样比较安全,但是由于 SH 没有“收集以 X 开头的变量”,我该怎么做?

【问题讨论】:

  • set | grep -e "^X" 收集以X 开头的变量名怎么样?
  • sh 不提供变量间接寻址,例如 ${!optionvarname},它试图评估名称存储在 optionvarname 中的变量。这是导致“错误替换”的仅限 bash 的功能......请注意您的错误消息 "In POSIX sh, indirect expansion is undefined." - 这就是告诉您的内容......
  • @DavidC.Rankin:我发现了这个问题,但遗憾的是我并不那么精通bash/sh 的差异和复杂性,以便能够进行适当的转换,因此寻求帮助

标签: bash sh parameter-expansion


【解决方案1】:

如何让这个脚本在我的路由器上运行?

很容易,要么:

  • 在您的路由器上安装 bash 或
  • 将脚本移植到busybox/posix 兼容的shell。

让 BusyBox 将脚本解释为 BASH 而不是 SH - 我可以为此使用特定的 shebang 吗?

这没有意义。 Busybox 带有ash shell 解释器,而 bash 就是 bash。 Bash 可以解释 bash 扩展,而 ash 不能解释它们。你不能“让busybox解释bash”——汽车不会飞,飞机就是为此。如果你想让汽车飞起来,你就给它加翅膀,让它飞得更快。 Make BusyBox interpret the script as BASH instead of SH 的答案是:修补 busybox 并在其中实现所有 bash 扩展。

Shebang 用于在不同的解释器下运行文件。使用#!/bin/bash 会调用bash,这与busybox 相关的任何内容都无关,busybox 也不会参与其中。

我该怎么做?

确定一个不切实际的最大值,遍历名为 foreign_option_{1...some_max} 的变量,查看每个变量是否已设置,如果已设置,则继续执行脚本。

for i in $(seq 100); do
    optionvarname="foreign_option_${i}"        
    # https://stackoverflow.com/questions/3601515/how-to-check-if-a-variable-is-set-in-bash
    if eval "[ -z \"\${${optionvarname}+x}\" ]"; then continue; fi;

如果有足够的运气,也许你可以使用set 输出。如果任何变量包含一个值作为换行符 + 与正则表达式匹配的字符串,则以下操作将失败:

for optionvarname in $(set | grep -o '^foreign_option_[0-9]\+=' | sed 's/=//'); then

间接扩展可以很容易地被eval代替:

eval "option=\"\$${optionvarname}\""
      

【讨论】:

  • 在我的路由器上,/bin/sh 是指向/bin/busybox 的符号链接。我会尝试“不切实际的最大值”建议(不是set 建议),然后我会告诉你结果如何。
【解决方案2】:

如果您确实无法在该路由器上安装 Bash,这是一种可能的解决方法,它似乎对我在 Qnap NAS 上的 BusyBox 中有效:

foreign_option_one=1
foreign_option_two=2

for x in one two; do
    opt_var=foreign_option_${x}
    eval "opt_value=\$$opt_var"
    echo "$opt_var = $opt_value"
done

(但您可能会在将 Bash 脚本移动到busybox 时遇到更多问题,因此您可能需要首先考虑替代路由器,例如更换路由器)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-11-22
    • 1970-01-01
    • 2016-07-23
    • 2013-11-01
    • 2019-06-23
    • 2015-02-09
    • 2018-11-15
    • 2011-01-23
    相关资源
    最近更新 更多