我的bash脚本面临一些奇怪的行为。它基本上是一个脚本,如果远程主机第一次失败,它会尝试多次ping。我这样做是为了排除任何错误警报。我想我可以通过编写一个递归函数来快速实现这一点,该函数调用自己并再次尝试ping。
我的问题是返回值。我发现该函数多次返回返回值,这与递归的次数相对应。这很奇怪。例如,在我下面的代码中,ip_up()函数应该为远程主机up返回1,为down返回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
我无法想象在许多情况下,我会经常使用循环中延迟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
状态为true,因此不执行|| 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
$
您的函数没有"返回"任何内容。它将一个值打印到stdout,每次调用都会这样做。
如果你想用这种机制模拟函数返回,你需要捕获并重新发送值:
Bash函数返回一个退出状态,这正如您所期望的那样(只要您期望0成功)。如果未指定其他值,则返回值为上一个命令的值。因此,以下方法可行:
tryn() {
if (($1 == 0)); then return 2; fi
"$@" || tryn $(($1-1)) "$@"
}
if tryn 2 ping $host; then
# success
fi