bash的陷阱(2): 函数返回值问题

一个脚本里遇到的,这个函数的写法是:

inner_stop(){
  local pid=$( get_service_pid )
  [ ! -z "$pid" ] && kill $pid
}

在调用完这个函数的地方需要检查一下它的执行结果,通过 $? 来判断,结果当前一句pid为空时,inner_stop 总会返回1。本来的意图是,判断当pid存在才执行kill,否则pid为空直接返回成功,而不是返回错误。这里忘了bash函数的返回值是上最后一条语句的执行结果 [ xxx ] 测试语句不满足时返回1

在返回值这点上要注意,不同于其他语言,bash属于弱类型语言,并且返回值总是最后一个命令的执行结果,if condition; then doSomething; fi 语句即可以返回if condition 的执行结果,也可以返回doSomething的结果。(注意这里说的结果是指return value,不是标准输出的内容)

#!/bin/bash

foo() {
  [ ! -z "$x" ] && echo "x not empty"
}

foo || echo "foo function return $?"

上面脚本执行时,如果没有全局声明过x变量,foo函数的返回总是1(错误),需要对另一个分支显式的return 0才行。

事先张扬的马拉松计划

以前就有过想法参加一次长城马拉松,前几天在42trip上看到这个赛事,犹豫了几天,发现已经错失了所有上半年可报的马拉松赛程,还是决定报名了金山岭长城马拉松(半程)。

看到有人评论,这个难度比较大,即使是半程也怀疑自己现在的状态是否能跑下来,因为身体的原因,已经半年多的时间没有跑步了,从现在算起还有11周时间。不管到时能否跑下来,不过至少这件事可以促使自己恢复跑步。

希望今年可以参加至少2次半程马拉松,上半年和下半年各一次。

执行情况:
2015.1.25 10公里,70分钟左右
2015.2.1 12公里,85分钟(下雪天)
2015.2.8 12公里,82分钟
2015.2.22 5公里
2015.3.1 12公里,75分钟
2015.3.29 15公里,105分钟
2015.4.19 21公里,金山岭长城马拉松(半程) 4小时59分

一个故事

晚上看到朋友圈里转的一个“凌晨三点半,一个陌生女人的来电”一文,讲述了五十年代发生在丹麦的故事,一位敏感而坚定的消防员救了一位在电话里说不清楚方位已经奄奄一息的女人;虽然最后文章变成了一篇招聘贴,但这个故事让我想起了两三年前还在做”来往”时遇到的一个事情。

我现在已经想不起这件事发生在2011年还是2012年(原帖已经找不到),那时候整个团队人不多,在阿里滨江园区用前CEO的办公室做项目室。当时来往还没有大规模推广用户量很少。有天晚上一个用户的帖子引起了大家的关注,原话大概是:老公我身上的血已经不多,请你快点回来。

看到这个帖子时,我感觉恶作剧的成分更多。后来是高倩同学说她直觉认为这不是一个玩笑;问有什么办法可以查到这个用户的联系方式,因为大家在这个帖子下面的回复劝阻并没能让这个用户再发言跟大家对话。

当时已经很晚,整个项目室就我一个人(那段时间习惯晚来晚走),我查到了这个用户的ip和手机号码,跟大家讨论后决定先打电话给用户问问情况。

在拨这个用户的手机号码时,非常不安,脑海中浮想过各种自杀的场景。电话铃声响了很久,我一边在构思该用怎样的话去挽留一个自杀的人,一边在想想对方可能是一个怎样的人:她可能是一个精神失常的人,该怎么对话?她可能已经奄奄一息?是在浴缸里用刀片割伤自己的手腕,血已染红整个浴缸?她是一个普通的家庭主妇,为了要挽留提出离婚的丈夫?

在我的不安和各种浮想联翩之后,电话超时,没人接听;过几分钟再打依然无人接听。这让我既感到轻松又感到失落,臆想的场景一个都没发生。很像电影《功夫》里周星驰去非正常人类研究中心请火云邪神时,想象着打开那扇门里面是如何的恐怖和血腥,打开后看到的却是一个普通的老人在安静地看着报纸。

拨打用户手机不通之后,我们选择了报警。用户的ip和手机号所在地并不一致,印象中一个是萍乡,一个是九江。先是打了IP所在地的110,接听电话的警官听完了我的陈述后,认为这是一件网络事件,并不属于刑事案件,不在他们管辖范围之内,给了我一个专门负责处理网络舆情的电话号码。我忘记了当时再打过去后,是没有打通还是对方仅仅是做了一个记录。

当时除了来往的同学,也有一些其他同事很关注这件事;在我打给IP地所在的公安局时,负责阿里邮箱的盛广也打给了手机号所在地区的公安局。后来还是公安机关联系到了他们,这个女的并未自杀,只是想引起她老公的重视。

事情过去好几年了,细节丢失了很多,大家权当个故事吧。

bash的陷阱(1): 逻辑或碰到local修饰符的情况

bash的函数如果有返回值,并要赋值给一个变量,跟普通的命令调用没有差异,都是: v=$(func) 的方式。需要注意的是,这里执行了fork操作,如果函数里有调用exit的话,只是让子进程退出,当前进程并不会退出。如果想要让当前进程也退出,可以使用set -e或在函数调用结束的时候再判断一次,比如:

v=$(func) || exit $?

如果函数调用失败,当前进程也退出。

今天遇到一个跟这个相关的陷阱,但并不是fork的问题,而是bash语法解析的问题,先看看我当时的写法,脚本简化后:

$ cat a.sh
#!/bin/bash

foo() {
    echo "error" && exit 1
}

bar() {
    local r=$( foo ) || exit $?
    echo $r # 执行了这里
}

bar

如上,函数bar以fork的方式调用foo,我们预期是foo返回值不为0时,bar函数也退出。所以上面bar函数里最后的echo $r不应该被执行到。但实际运行的时候发现这段代码确实可以执行到bar函数的最后一行。

后来,去掉了local修饰符,执行符合预期了:

bar() {
    r=$( foo ) || exit $?
    echo $r # 不会执行到这里
}

看来是 r=$( foo ) || exit $? 里的逻辑或||被解析器作为等号后边两个命令的逻辑运算;而前边加了local修饰符之后,逻辑或||被当成表达式local r=$(foo) 与 命令 exit 之间的逻辑运算。