【问题标题】:Scoping issue memoising bash function with associative array范围问题记忆具有关联数组的 ba​​sh 函数
【发布时间】:2016-03-15 17:50:03
【问题描述】:

我有一个 bash 脚本,它使用 jq 在一些 JSON 中查找“依赖”数据,并采用闭包(查找依赖项的依赖项等)。

这很好用,但可能会很慢,因为它可能会一遍又一遍地查找相同的依赖项,所以我想记住它。

我尝试使用全局关联数组将参数与结果相关联,但该数组似乎没有存储任何内容。

我已将相关代码提取到以下demo中:

#!/usr/bin/env bash

# Associative arrays must be declared; use 'g' for global
declare -Ag MYCACHE
function expensive {
    # Look up $1 in MYCACHE
    if [ "${MYCACHE[$1]+_}" ]
    then
        echo "Using cached version" >> /dev/stderr
        echo "${MYCACHE[$1]}"
        return
    fi

    # Not found, perform expensive calculation
    RESULT="foo"

    echo "Caching result" >> /dev/stderr
    MYCACHE["$1"]="$RESULT"

    # Check if the result was cached
    if [ "${MYCACHE[$1]+_}" ]
    then
        echo "Cached" >> /dev/stderr
    else
        abort "Didn't cache"
    fi

    # Done
    echo "$RESULT"
}

function abort {
    echo "$1" >> /dev/stderr
    exit 1
}

# Run once, make sure result is "foo"
[[ "x$(expensive "hello")" = "xfoo" ]] ||
    abort "Failed for hello"

# Run again, make sure "Using cached version" is in stderr
expensive "hello" 2>&1 > /dev/null | grep "Using cached version" ||
    abort "Didn't use cache"

这是我的结果:

$ ./foo.sh 
Caching result
Cached
Didn't use cache

我们得到Cached 的事实似乎表明我正在正确存储和查找值,但由于我们点击了Didn't use cache 分支,因此它们没有在expensive 的调用中保留。

对我来说,这似乎是一个范围界定问题,可能是由 declare 引起的。但是,declare -A 似乎是使用关联数组的要求。

这是我的 bash 版本:

$ bash --version
GNU bash, version 4.3.42(1)-release (i686-pc-linux-gnu)
Copyright (C) 2013 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>

This is free software; you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

除了弄清楚我正在经历的行为之外,我还喜欢在 bash 中记忆函数的替代方法(最好不要触及文件系统,即使它是基于 RAM 的)

【问题讨论】:

    标签: bash scope associative-array memoization


    【解决方案1】:

    你有几个问题:

    1. declare -g 仅在函数内部有意义。在外面,一个变量已经是全局的了。

    2. 全局变量仅对声明它的进程是全局的。您不能跨进程共享全局变量。

    3. 在命令替换中运行 expensive 会在单独的进程中执行此操作,因此它创建和填充的缓存会随着该进程而消失。

    4. 运行expensive作为管道的第一个命令也会创建一个新进程;它使用的缓存只对该进程可见。

    您可以通过确保 expensive 仅在当前 shell 中运行来解决此问题

    expensive "hello" > tmp.txt && read result < tmp.txt
    [[ $foo = foo ]] || abort ...
    expensive "hello" 2>&1 > /dev/null < <(grep "Using cached version") ||
    abort "Didn't use cache"
    

    但是,Shell 脚本根本不适合这种类型的数据处理。如果缓存很重要,请使用更好地支持数据结构和内存中数据处理的不同语言。 Shell 针对启动新进程和管理输入/输出文件进行了优化。

    【讨论】:

    • 你的第 3 点似乎有点不对劲;你将grep 输入expensive,而不是反过来(它给出grep: (standard input): Input/output error
    • 看起来[[ "x$(...)" = "xfoo" ]] 行是使用子进程的行,因此不会填充缓存。 grep 行实际上按原样工作,如果在其上方放置 expensive 调用以填充缓存。
    • 对,我没有仔细阅读原文。管道对我来说更加突出,但 $(...) also 在子进程中运行 expensive
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-02-20
    • 2013-02-25
    • 1970-01-01
    • 1970-01-01
    • 2013-06-25
    • 2011-11-13
    • 1970-01-01
    相关资源
    最近更新 更多