Bash 中的尾递归

Tail recursion in Bash

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

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([=11=],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([=12=],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([=13=],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([=13=],a,","); for (i=1; i<=n; i++) print a[i]}'
>     > ++ sed 's/\"\:\"/\|/g'
>     > ++ cut -d : -f 2
>     > ++ sed 's/\"//g'
>     > ++ grep -w cacheSize

我猜它甚至没有迭代。

通过将 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 does not inherently support floating point arithmetic (comparison in your case), use a third party tool like bc, awk for this,

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

您可以使用 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 "[=10=]: 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 >/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 "[=11=]: 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 "[=11=]: try $try failed; aborting" >&2 ;;
              *) echo "[=11=]: try $try failed; sleeping before retrying" >&2
                 sleep 0.5 ;;
             esac;;
          *) return 0
        esac
    done
    return 1
}

嵌套的 case 以避免在最后一次迭代时休眠并不是特别优雅,但至少它应该确保 reader 是清醒的。 /-8