【问题标题】:Tail recursion in BashBash 中的尾递归
【发布时间】:2017-03-01 08:48:24
【问题描述】:

在使用该服务进行任何进一步更改之前,我尝试编写一个脚本来验证指标的所有统计数据是否为正。我坚持的部分是考虑如何为以下用例尾随递归:

function load_cache() {
    cacheStat=( $(curl -s -X GET "http://localhost:${MET_PORT}/metrics" | sed 's/\\\\\//\//g' | sed 's/[{}]//g' | awk -v k="cacheSize" '{n=split($0,a,","); for (i=1; i<=n; i++) print a[i]}' | sed 's/\"\:\"/\|/g' | sed 's/[\,]/ /g' | sed 's/\"//g' | grep -w "cacheSize" | cut -d ':' -f 2) )

    # the above gives me the ouput(cacheStat) as -
    # 2.0
    # 311.0
    # 102.0

    count=0
    for index in ${!cacheStat[*]}
    do
        if [[ ${cacheStat[$index]} -le 0 ] && [ $count -lt 3 ]]; then
            sleep .5
            count=$[$count +1];
            load_cache
            #Wouldn't the above initialise `count` to 0 again.
        fi
    done
}

我想要做的是,如果 cacheStat 中的任何元素小于或等于 0,然后休眠 .5 秒并再次查询 cacheStat 并再次对其所有元素执行检查。虽然这样做的次数不超过 3 次,但我尝试使用 `count.

欢迎任何改进脚本的建议。


更新 - 根据@Inian 的建议将脚本修改为

RETRY_COUNT=0
function load_cache() {
    cacheStat=( $(curl -s -X GET "http://localhost:${MET_PORT}/metrics" | sed 's/\\\\\//\//g' | sed 's/[{}]//g' | awk -v k="cacheSize" '{n=split($0,a,","); for (i=1; i<=n; i++) print a[i]}' | sed 's/\"\:\"/\|/g' | sed 's/[\,]/ /g' | sed 's/\"//g' | grep -w "cacheSize" | cut -d ':' -f 2) );
    for index in ${!cacheStat[*]}
    do
        echo "Stat - ${cacheStat[$index]}"
        if (( ${cacheStat[$index]} <= 0 )) && (( $RETRY_COUNT < 3 )); then
            echo "Attempt count - ${RETRY_COUNT}"
            sleep .5s
            RETRY_COUNT=$((RETRY_COUNT +1));
            load_cache
        fi
    done
}

读取的日志 -

>     > + cacheStat=($(curl -s -X GET "http://localhost:${MET_PORT}/metrics" | sed 's/\\\\\//\//g' | sed
> 's/[{}]//g' | awk -v k="cacheSize"
>     > '{n=split($0,a,","); for (i=1; i<=n; i++) print a[i]}' | sed
>     > 's/\"\:\"/\|/g' | sed 's/[\,]/ /g' | sed 's/\"//g' | grep -w
>     > "cacheSize" | cut -d ':' -f 2))
>     > ++ curl -s -X GET http://localhost:8181/metrics
>     > ++ sed 's/\\\\\//\//g'
>     > ++ sed 's/[{}]//g'
>     > ++ sed 's/[\,]/ /g'
>     > ++ awk -v k=cacheSize '{n=split($0,a,","); for (i=1; i<=n; i++) print a[i]}'
>     > ++ sed 's/\"\:\"/\|/g'
>     > ++ cut -d : -f 2
>     > ++ sed 's/\"//g'
>     > ++ grep -w cacheSize

我猜它甚至不会迭代。

【问题讨论】:

  • 您的脚本有几个语法问题。在尝试之前修复它,你确定这有效吗?
  • @Inian 没有。这就是为什么要分享它。并且请您指出其中的缺陷。把我当成一个 bash 的新手。尝试执行脚本时,我看到的只是count=0,之前什么都没有。即使我把echo 放在那里。
  • 参考下面我的答案,首先通过将count移到函数外来消除无限递归。
  • 看起来你无论如何都在模糊地硬编码一个穷人的 JSON 解码器。尝试转向支持 JSON 的语言,例如 jq。
  • 如果你的意思是sed sn-p,这有点投机。如果您需要支持浮点,则正则表达式需要稍微复杂一些。无论如何,为了便于阅读,我仍然会选择jq。

标签: bash recursion tail-recursion


【解决方案1】:

通过将count=0移出函数体来移除无限递归。

您的脚本还有几个问题,语法违规和过时的结构,12-14 行应该是,

if [[ ${cacheStat[$index]} -le 0 ]] && [[ $count -lt 3 ]]; then
    sleep .5s
    count=$((count +1));
    load_cache
fi

或)在if-clause as 中使用更易读的算术运算符(())

if (( ${cacheStat[$index]} <= 0 )) && (( $count < 3 )); then

bash 本身不支持浮点运算(在您的情况下进行比较),为此请使用第三方工具,如 bc、awk,

if (( $(echo "${cacheStat[$index]} <= 0" | bc -l) )) && (( $count < 3 )); then

【讨论】:

  • 此外,比较当前的输出为syntax error: invalid arithmetic operator (error token is ".0 &lt;= 0.0 ")。因为我们的输入变成了2.0
  • 您无法比较 bash 中的浮点值。不为你工作,并不意味着答案不正确,不接受它。
  • 嗯,我明白了。我想我会把它转换为 int 。谢谢。
【解决方案2】:

您可以使用 JSON 解析器避免所有即席 JSON 解析。

# Avoid using Bash-only "function" keyword
load_cache () {
    local try
    for try in 1 2 3; do
        # Suction: jq doesn't return non-zero exit code for no match
        # work around that by piping to grep .
        if curl -s -X GET "http://localhost:${MET_PORT}/metrics" |
            jq '.[] | select(cacheSize < 0)' |
            grep .
        then
            # Notice also redirection to stderr for diagnostic messages
            echo "$0: Attempt $try failed, sleeping before retrying" >&2
            sleep 0.5
        else
            # Return with success, we are done, exit function
            return 0
        fi
    done

    # Return failure
    return 1
}

我认为没有理由更喜欢递归而不是简单的 for 循环来控制重试次数。

如果您不想看到有问题的值,可以在条件中使用grep -q。如果你不想要输出,我希望你会做load_cache &gt;/dev/null。

如果您想查看没有违规的值,则代码需要进行一些重构,但我专注于优雅简洁地完成核心工作。这是一个草图,主要是向您展示jq 的语法。

load_cache () {
    local try
    local results
    for try in 1 2 3; do
        results=$(curl -s -X GET "http://localhost:${MET_PORT}/metrics" |
            jq '.[] | .cacheSize' | tr '\n' ' ')
        echo "$0: try $try: cacheSize $results" >&2
        # Funky: massage the expression we test againt into a normalized form
        # so that we know that the value will always be preceded by a space
        case " $results " in
          *" 0 "* | *" -"* )
             case $try in
              3) echo "$0: try $try failed; aborting" >&2 ;;
              *) echo "$0: try $try failed; sleeping before retrying" >&2
                 sleep 0.5 ;;
             esac;;
          *) return 0
        esac
    done
    return 1
}

嵌套case 以避免在最终迭代中休眠并不是特别优雅,但至少它应该确保读者是清醒的。 /-8

【讨论】:

    猜你喜欢
    • 2016-03-21
    • 2018-03-17
    • 1970-01-01
    • 2017-11-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-02-11
    相关资源
    最近更新 更多