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