【问题标题】:Return value from Linux bash recursive functionLinux bash 递归函数的返回值
【发布时间】:2016-06-04 13:09:02
【问题描述】:

我的 bash 脚本面临一些奇怪的行为。它基本上是一个脚本,如果它第一次失败,它会尝试多次 ping 远程主机。我这样做是为了排除任何虚假警报。我想我可以通过编写一个调用自身并再次尝试 ping 的递归函数来快速实现这一点。

我的问题是返回值。我发现该函数多次返回与递归次数相对应的返回值。这很奇怪。例如,在我下面的代码中,ip_up() 函数应该返回 1 表示远程主机启动,返回 0 表示关闭。但是,当远程主机关闭时,该函数返回 0 两次,这对应于所做的递归。

我的代码可能有什么问题,或者这就是 bash 的工作原理?

#!/bin/bash
    ip_up(){
            server_ip=$1
            trials=$2
            max_trials=2
            status=0
            echo "server ip is: $server_ip, trial $trials" >&2
            if ping -i 1 -c 3 "$server_ip" &> /dev/null
            then
                status=1
            else
                status=0
                while ((  "$trials" < "$max_trials" )); do
                    echo -e "$server_ip is down: Trial $trials, checking again after 1 sec" >&2
                    sleep 1
                    ((trials++))
                    ip_up "$server_ip" "$trials"
                done
            fi
            echo "$status"
    }

    status=$(ip_up "$ip" 1)
    echo -e "the returned status is: ====$status====\n"
   if [ "$server_status" -eq 0 ]; then
        msg="$timestamp: Server $hostname ($ip) is DOWN"; echo "$msg"
   fi

    <<'COMMENT'
    //results

     $ ./check_servers.sh
    checking box1(173.36.232.6)
    server ip is: 173.36.232.6, trial 1
    173.36.232.6 is down: Trial 1, checking again after 1 sec
    server ip is: 173.36.232.6, trial 2
    the returned status is: ====0
    0====

    ./check_servers.sh: line 41: [: 0
    0: integer expression expected
    Sat Jun  4 15:16:11 EAT 2016 box2 (173.36.232.7) is UP
    checking box2 (173.36.232.7)
    server ip is: 173.36.232.7, trial 1
    the returned status is: ====1====

    COMMENT

【问题讨论】:

  • 这主要是关于递归一般如何工作的问题,因此 Bash 在实现递归时如何工作。对函数的每次递归调用最终都会返回;每次调用返回一个。如果您不想要递归和多次返回,请改用迭代。事实上,您的代码中已经有了迭代;它只是迭代地递归——这很奇怪。如果你有if ping,你应该使用while 循环,它可以测试迭代计数和ping 状态。整本书需要认真重写。
  • 感谢@JonathanLeffler 的回答。这当然是有道理的,我已经根据你的建议写了一篇文章。为了学习bash中的递归,你将如何实现它?
  • 恕我直言,我不会递归地实现代码——句号。好吧,除非它是用 LISP 编写的,否则我可能别无选择。学习递归定义的问题的递归。我将提供一个答案来展示我将如何编写该过程,但它不会涉及递归。如果您坚持需要递归,请找其他人帮忙。

标签: linux bash recursion


【解决方案1】:

我无法想象在很多情况下我会在循环中使用具有一秒延迟的代码,这足以使其值得作为一个函数编写——我会使用一个相对直接(迭代)的脚本。但是,如果您确定这对您有利,那么将脚本转换为函数远非不可能;你的情况和我的不一样。

#!/bin/sh

[ $# = 1 ] || [ $# = 2 ] || { echo "Usage: $0 ip-address [max-trials]" >&2; exit 1; }
server_ip="$1"
maxtrials="${2:-2}"
trial=1

while echo "server: $server_ip, trial $trial" >&2
      ! ping -i 1 -c 3 "$server_ip" > /dev/null 2>&1 || exit 0
do
    trial=$(($trial + 1))
    [ "$trial" -gt "$maxtrials" ] && break
    echo "$0: $server_ip is down: checking again after 1 sec" >&2
    sleep 1
done

echo "$(date +'%Y-%m-%d %H:%M:%S'): Server $server_ip is DOWN"
exit 1

第一个代码块设置控件,默认为 2 次尝试。

while 循环控件包含echo,然后尝试ping IP 地址(或主机名)。如果命令成功(主机可ping),则! ping状态为false,因此执行|| exit 0,脚本以0状态退出,表示成功(主机可ping)。如果命令失败(主机无法ping通),那么! ping状态为真,所以|| exit 0不执行,进入循环体。如果达到限制,它会增加试用号并中断循环。否则,它会打印它的消息并休眠并返回到循环的开头。

只有在exit 0 没有被执行时才会到达结束块,所以ping 失败并且服务器“关闭”(或不存在)。然后,您会收到一条带时间戳的消息,指示服务器已关闭,并以非零状态退出以指示失败。

可能有无数其他方法可以做到这一点。我可能与错误消息更一致——例如,我可能会保存arg0="$(basename "$0" .sh)",然后使用$arg0 作为所有消息的前缀(或者可能在时间戳之后添加它)。可以调整它以报告服务器已启动。该代码适用于 POSIX shell,而不仅仅是 Bash(例如,dash 像 Korn shell 一样接受它,但 Heirloom (Bourne) Shell 不接受它,因为它不喜欢 $(…) 或 $((…)) )。

也可以将其编写为一个简单的计数循环,用于测试ping 的状态,成功退出,然后进行报告和重试。但是,要避免最后一个sleep 1 是很棘手的,因为循环将退出而不对$trial 的值进行双重测试。这在运行时并不昂贵,但它是重复的来源,而 DRY(不要重复自己)是值得遵守的原则。

#!/bin/bash

[ $# = 1 ] || [ $# = 2 ] || { echo "Usage: $0 ip-address [max-trials]" >&2; exit 1; }
server_ip="$1"
maxtrials="${2:-2}"

for ((trial = 1; trial <= maxtrials; trial++))
do
    echo "server: $server_ip, trial $trial" >&2
    if ping -i 1 -c 3 "$server_ip" > /dev/null 2>&1
    then exit 0
    elif [ "$trial" -lt "$maxtrials" ]
    then
        echo "$0: $server_ip is down: checking again after 1 sec" >&2
        sleep 1
    fi
done

echo "$(date +'%Y-%m-%d %H:%M:%S'): Server $server_ip is DOWN"
exit 1

我并不完全热衷于此,但它适用于 Bash 和 Korn shell。

将最后一个脚本转换为函数基本上是微不足道的——将exit 语句更改为return 语句,并在其周围包装一个函数开始和结束:

#!/bin/bash

function upip()
{
    [ $# = 1 ] || [ $# = 2 ] || { echo "Usage: $0 ip-address [max-trials]" >&2; return 1; }
    server_ip="$1"
    maxtrials="${2:-2}"

    for ((trial = 1; trial <= maxtrials; trial++))
    do
        echo "server: $server_ip, trial $trial" >&2
        if ping -i 1 -c 3 "$server_ip" > /dev/null 2>&1
        then return 0
        elif [ "$trial" -lt "$maxtrials" ]
        then
            echo "$0: $server_ip is down: checking again after 1 sec" >&2
            sleep 1
        fi
    done

    echo "$(date +'%Y-%m-%d %H:%M:%S'): Server $server_ip is DOWN"
    return 1
}

保存在upip-func.sh,我看了函数:

$ . upip-func.sh
$ upip www.google.com
server: www.google.com, trial 1
$ echo $?
0
$ upip ping.google.com
server: ping.google.com, trial 1
bash: ping.google.com is down: checking again after 1 sec
server: ping.google.com, trial 2
2016-06-06 00:35:18: Server ping.google.com is DOWN
$ echo $?
1
$ if upip www.google.com; then echo OK; else echo Fail; fi
server: www.google.com, trial 1
OK
$ if upip ping.google.com; then echo OK; else echo Fail; fi
server: ping.google.com, trial 1
bash: ping.google.com is down: checking again after 1 sec
server: ping.google.com, trial 2
2016-06-06 00:38:32: Server ping.google.com is DOWN
Fail
$

【讨论】:

  • 谢谢@jonathan。这很好解释。如果我将它包装在一个 bash 函数中,比如在脚本的某个部分调用的 ip_up,我如何测试成功或失败?例如,我可以使用 if 语句,例如 if ip_up;然后回显“服务器已启动”;菲。
  • 您需要提供“IP 地址”或主机名作为参数,但可以:if ip_up www.google.com; then echo server is up; fi。我使用“www.google.com”和“ping.google.com”进行正面和负面测试。当然,您可以在函数中使用return 0 代替exit 0(和return 1 代替exit 1)。我相信其余的应该基本不变。
  • 我已将 exit 替换为 return,但它没有返回任何内容。如果我想返回一个值,我必须回显它。我想我只需要对函数进行 if 测试。非常感谢。
  • 是的,确实如此:它返回 0(成功)或 1(失败)作为状态,可以通过 if up_ip www.google.com; then echo "OK"; fi 进行测试。这是一个可测试的状态,而不是输出中的字符串。您没有看到来自测试命令[ 的任何输出,是吗?你也不应该期望看到up_ip 函数的输出——除非你决定让所有的冗长都变成标准错误。
【解决方案2】:

你的函数没有“返回”任何东西。它将一个值打印到标准输出,每次调用都会这样做。

如果你想用这种机制模拟一个函数返回,你需要捕获并重新发送值:

Bash 函数返回一个退出状态,这正如您所期望的那样工作(只要您期望 0 成功)。如果您没有另外指定,则返回值是最后一个命令的值。所以下面会起作用:

tryn() {
  if (($1 == 0)); then return 2; fi
  "$@" || tryn $(($1-1)) "$@"
}

if tryn 2 ping $host; then
   # success
fi

【讨论】:

  • 谢谢@rici。我已经读过要在 bash 中返回一个值,您回显该值而不是使用 return。事实上,我尝试使用 return,但它从未返回所需的值。
猜你喜欢
  • 2020-10-10
  • 2021-06-11
  • 2012-03-11
  • 2017-05-14
  • 2014-07-13
  • 2022-11-05
  • 2017-09-24
  • 2021-03-02
相关资源
最近更新 更多