从 bash 脚本启动进程失败

3
我可以帮助您翻译以下内容,涉及IT技术。请注意,我将保留HTML标记并尝试让内容更加通俗易懂。

我有一个中央服务器,在其中定期启动一个脚本(来自cron),用于检查远程服务器。检查是串行进行的,所以先是一个服务器,然后是另一个...。

这个脚本(来自中央服务器)在远程机器上启动另一个脚本(我们称之为update.sh),而那个脚本(在远程机器上)正在执行以下操作:

processID=`pgrep "processName"` 
kill $processID
startProcess.sh

进程被杀死,然后在脚本startProcess.sh中启动,如下所示:
pidof "processName"

if [ ! $? -eq 0 ]; then
    nohup "processName" "processArgs" >> "processLog" &
    pidof "processName"
    if [! $? -eq 0]; then
        echo "Error: failed to start process"
...

update.sh、startprocess.sh以及启动进程的实际二进制文件都是从中央服务器挂载的NFS。

现在,有时候我尝试在startprocess.sh中启动的进程没有启动,并出现错误。奇怪的是,这是随机的,有时候同一台机器上的进程会启动,而另一次不会启动。我正在检查大约300个服务器,错误总是随机发生。

还有一件事,远程服务器位于3个不同的地理位置(2个在美国,1个在欧洲),中央服务器在欧洲。目前我发现的情况是,美国的服务器比欧洲的服务器有更多的错误。

起初我以为错误肯定与kill有关,所以我在kill和startprocess.sh之间加了一个休眠时间,但这没有任何效果。

另外,似乎startprocess.sh中的进程根本没有启动,或者在启动时发生了什么问题,因为日志文件中没有输出,而日志文件中应该有输出。

所以,我在这里请求帮助。

有人遇到过这种问题吗,或者知道可能出了什么问题?

感谢任何帮助


1
我怀疑你在Server Fault上可能会比在Stack Overflow上表现更好。你的症状听起来像是跨大西洋连接速度较慢,NFS操作更容易超时。如果软件是自动挂载的,可能是命令失败时相关目录不可用,但命令成功时可用;我以前遇到过这样的问题。还有“如果你有足够多的机器,总会有一些出现故障”的综合症。与数百台机器相比,使用数千台机器会更加困难。 - Jonathan Leffler
1个回答

4

(很抱歉,我的原始答案相当错误... 这里是更正)

startProcess.sh中使用$?来获取后台进程的退出状态会导致错误的结果。手册中说明:

Special Parameters
?      Expands to the status of the most recently executed foreground
       pipeline.

如您在评论中提到的,获取后台进程的退出状态的正确方法是使用内置的wait。但是为了实现这一点,必须处理SIGCHLD信号。
我为此创建了一个小型测试环境,以展示它如何工作:
以下是要作为后台进程运行的脚本loop.sh
#!/bin/bash
[ "$1" == -x ] && exit 1;
cnt=${1:-500}
while ((++c<=cnt)); do echo "SLEEPING [$$]: $c/$cnt"; sleep 5; done

如果arg为-x,则以退出状态1退出以模拟错误。如果arg为num,则等待num*5秒并打印SLEEPING [<PID>] <counter>/<max_counter>到stdout。
第二个是启动器脚本。它在后台启动3个loop.sh脚本并打印它们的退出状态:
#!/bin/bash

handle_chld() {
    local tmp=()
    for i in ${!pids[@]}; do
        if [ ! -d /proc/${pids[i]} ]; then
            wait ${pids[i]}
            echo "Stopped ${pids[i]}; exit code: $?"
            unset pids[i]
        fi
    done
}

set -o monitor
trap "handle_chld" CHLD

# Start background processes
./loop.sh 3 &
pids+=($!)
./loop.sh 2 &
pids+=($!)
./loop.sh -x &
pids+=($!)

# Wait until all background processes are stopped
while [ ${#pids[@]} -gt 0 ]; do echo "WAITING FOR: ${pids[@]}"; sleep 2; done
echo STOPPED

handle_chld函数将处理SIGCHLD信号。设置选项monitor使非交互式脚本能够接收SIGCHLD。然后对SIGCHLD信号设置陷阱。

然后启动后台进程。它们所有的PID都被记在pids数组中。如果收到SIGCHLD,则在/proc/目录下检查哪个子进程停止了(缺少的那个)(也可以使用kill -0 <PID>命令进行检查)。等待后,后台进程的退出状态存储在著名的$?伪变量中。

主脚本等待所有PID停止(否则无法获取其子进程的退出状态),然后停止自身。

一个示例输出:

WAITING FOR: 13102 13103 13104
SLEEPING [13103]: 1/2
SLEEPING [13102]: 1/3
Stopped 13104; exit code: 1
WAITING FOR: 13102 13103
WAITING FOR: 13102 13103
SLEEPING [13103]: 2/2
SLEEPING [13102]: 2/3
WAITING FOR: 13102 13103
WAITING FOR: 13102 13103
SLEEPING [13102]: 3/3
Stopped 13103; exit code: 0
WAITING FOR: 13102
WAITING FOR: 13102
WAITING FOR: 13102
Stopped 13102; exit code: 0
STOPPED

可以看到,退出代码被正确报告。
希望这能有所帮助!

谢谢你的帮助,我按照你写的做了。我还加了一个等待进程的命令'wait $PID',如果'ps -p $PID'的返回代码不为零 -> 意味着发生了一些问题。当我获取到返回代码后我会在这里再次发布消息。 - Jan
@Jan:你能解决这个问题吗?返回的错误代码是什么? - TrueY
@Jan:你能解决这个问题吗?返回的错误代码是什么? - TrueY
是的。问题出在pidof命令上。在我的问题中,我写道我正在检查nohup的返回代码$?,但实际脚本中我并没有这样做,而是在nohup之后立即检查了pidof的$?。总之,除了检查“?!”是否存在之外,一切都正常工作,我正在执行pidof“命令名称”以查看它是否正确启动,这导致竞争条件,这就是为什么错误是随机的原因。我将编辑我的问题,以反映我的实际代码。无论如何,您的答案确实帮助了我,非常感谢。 - Jan
@Jan:谢谢!我还稍微修改了一下我的代码,让它变得更加智能了一些。 - TrueY
不错。非常感谢。 - Alejandro Teixeira Muñoz

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接