当“nounset”选项生效时,获取空数组或未设置数组的长度

时间:2016-07-11 17:30:03

标签: bash

由于Bash在set -o nounset模式下运行(又名set -u)时,可能会认为空数组未设置,无论它们是否实际分配了空值,必须小心尝试扩展数组时 - 其中一个解决方法是检查数组长度是否为零。更不用说获取数组中的元素数量本身就是一种常见的操作。

在openSUSE 42.1中使用Bash 4.2.47(1) - release发展时,我习惯于当数组为空或未设置时,使用${#ARRAY_NAME[@]}获取数组大小成功。但是,在FreeBSD 10.3中使用Bash 4.3.46(1)-release检查我的脚本时,结果发现此操作可能会失败并显示通用的“未绑定变量”错误消息。提供扩展的默认值似乎不适用于数组长度。 提供替代命令链似乎可行,但不在通过子shell扩展调用的函数内 - 函数在第一次失败后退出。还有什么可以帮助吗?

考虑以下示例:

function Size ()
{
    declare VAR="$1"
    declare REF="\${#${VAR}[@]}"
    eval "echo \"${REF}\" || echo 0" 2>/dev/null || echo 0
}

set -u
declare -a MYARRAY

echo "size: ${#MYARRAY[@]}"
echo "size: ${#MYARRAY[@]-0}"
echo "Size: $(Size 'MYARRAY')"
echo -n "Size: "; Size 'MYARRAY'

在openSUSE环境中,所有echo行都按预期输出0。在FreeBSD中,只有在数组明确赋值为空值时才能获得相同的结果:MYARRAY=();否则,前两行中的内联查询都会失败,第三行只输出Size:(意味着扩展结果为空),并且由于外部|| echo 0,只有最后一行完全成功 - 但是将结果传递到屏幕并不是通常在尝试获取数组长度时的意图。

以下是我的观察摘要:

                                    Bash 4.2  Bash 4.3
                                    openSUSE  FreeBSD

counting elements of unset array       OK      FAILED
counting elements of empty array       OK        OK

content expansion of unset array     FAILED    FAILED
content expansion of unset array(*)    OK        OK
content expansion of empty array     FAILED    FAILED
content expansion of empty array(*)    OK        OK
    (* with fallback value supplied)

对我来说,这看起来很不一致。有没有真正面向未来的跨平台解决方案呢?

2 个答案:

答案 0 :(得分:1)

bash的Linux和BSD风格之间存在已知(记录)的差异。我建议按照POSIX标准编写代码。您可以从这里开始获取更多信息 - > www2.opengroup.org。

考虑到这一点,您可以使用bash命令行选项启动--posix,也可以在set -o posix运行时执行命令bash。两者都会导致bash符合POSIX标准。

上述建议将增加跨平台一致性的可能性。

答案 1 :(得分:1)

作为临时解决方案,我遵循@ william-pursell建议的路线,并在查询期间取消设置nounset选项:

function GetArrayLength ()
{
    declare ARRAY_NAME="$1"
    declare INDIRECT_REFERENCE="\${#${ARRAY_NAME}[@]}"
    case "$-" in
    *'u'*)
        set +u
        eval "echo \"${INDIRECT_REFERENCE}\""
        set -u
        ;;
    *)
        eval "echo \"${INDIRECT_REFERENCE}\""
        ;;
    esac
}

(使用if代替case会导致我的测试计算机上的执行速度低得多。此外,case允许轻松匹配其他选项,如果有时需要这样做。)

我还试图利用这样一个事实:即使对于未设置的数组,内容扩展(具有后备或替换值)通常也会成功:

function GetArrayLength ()
{
    declare ARRAY_NAME="$1"
    declare INDIRECT_REFERENCE="${ARRAY_NAME}[@]"
    if [[ -z "${!INDIRECT_REFERENCE+isset}" ]]; then
        echo 0
    else
        INDIRECT_REFERENCE="\${#${ARRAY_NAME}[@]}"
        eval "echo \"${INDIRECT_REFERENCE}\""
    fi
}

然而,事实证明Bash并没有优化${a[@]+b}扩展,因为较大阵列的执行时间明显增加 - 尽管是空数组或未设置数组的最小值。

然而,如果有人有更好的解决方案,可以自由发布其他答案。