Bash:等待超时

2024-11-07 08:55:00
admin
原创
26
摘要:问题描述:在 Bash 脚本中,我想执行如下操作:app1 & pidApp1=$! app2 & pidApp2=$! timeout 60 wait $pidApp1 $pidApp2 kill -9 $pidApp1 $pidApp2 即在后台启动两个应用程序,并给它们 60 秒的时间...

问题描述:

在 Bash 脚本中,我想执行如下操作:

app1 &
pidApp1=$!
app2 &
pidApp2=$!

timeout 60 wait $pidApp1 $pidApp2
kill -9 $pidApp1 $pidApp2

即在后台启动两个应用程序,并给它们 60 秒的时间来完成工作。然后,如果它们没有在这段时间内完成,就终止它们。

不幸的是,上面的代码不起作用,因为timeout是可执行文件,而wait是 shell 命令。我尝试将其更改为:

timeout 60 bash -c wait $pidApp1 $pidApp2

但这仍然不起作用,因为wait只能在同一个 shell 内启动的 PID 上调用。

有什么想法吗?


解决方案 1:

您的示例和接受的答案都过于复杂,为什么您不只使用timeout因为这正是的用例?该timeout命令甚至有一个内置选项(-k),如果命令在发送SIGKILL初始信号SIGTERM后仍在运行,则在发送初始信号后发送以终止命令(默认情况下)(请参阅)man timeout

如果脚本不一定需要wait等待后恢复控制流,那么只需

timeout -k 60s 60s app1 &
timeout -k 60s 60s app2 &
# [...]

但是,如果确实如此,那么只需保存timeoutPID 即可轻松完成:

pids=()
timeout -k 60s 60s app1 &
pids+=($!)
timeout -k 60s 60s app2 &
pids+=($!)
wait "${pids[@]}"
# [...]

例如

$ cat t.sh
#!/bin/bash

echo "$(date +%H:%M:%S): start"
pids=()
timeout 10 bash -c 'sleep 5; echo "$(date +%H:%M:%S): job 1 terminated successfully"' &
pids+=($!)
timeout 2 bash -c 'sleep 5; echo "$(date +%H:%M:%S): job 2 terminated successfully"' &
pids+=($!)
wait "${pids[@]}"
echo "$(date +%H:%M:%S): done waiting. both jobs terminated on their own or via timeout; resuming script"

$ ./t.sh
08:59:42: start
08:59:47: job 1 terminated successfully
08:59:47: done waiting. both jobs terminated on their own or via timeout; resuming script

解决方案 2:

将 PID 写入文件并启动应用程序,如下所示:

pidFile=...
( app ; rm $pidFile ; ) &
pid=$!
echo $pid > $pidFile
( sleep 60 ; if [[ -e $pidFile ]]; then killChildrenOf $pid ; fi ; ) &
killerPid=$!

wait $pid
kill $killerPid

这将创建另一个进程,该进程会因超时而处于休眠状态,如果该进程尚未完成,则会终止该进程。

如果该进程完成得更快,则删除 PID 文件并终止杀手进程。

killChildrenOf是一个获取所有进程并终止特定 PID 的所有子进程的脚本。请参阅此问题的答案,了解实现此功能的不同方法:终止所有子进程的最佳方法

如果您想脱离 BASH,可以将 PID 和超时写入目录并监视该目录。每隔一分钟左右,读取条目并检查哪些进程仍然存在以及它们是否已超时。

编辑如果您想知道该进程是否已成功终止,您可以使用kill -0 $pid

EDIT2或者您可以尝试进程组。kevinarpe说:要获取 PID(146322) 的 PGID :

ps -fjww -p 146322 | tail -n 1 | awk '{ print $4 }'

在我的情况下是:145974。然后可以使用 PGID 和特殊选项 kill 来终止组中的所有进程:kill -- -145974

解决方案 3:

以下是 Aaron Digulla 答案的简化版本,它使用了kill -0Aaron Digulla 在评论中留下的技巧:

app &
pidApp=$!
( sleep 60 ; echo 'timeout'; kill $pidApp ) &
killerPid=$!

wait $pidApp
kill -0 $killerPid && kill $killerPid

就我而言,我希望既set -e -x安全又返回状态代码,因此我使用了:

set -e -x
app &
pidApp=$!
( sleep 45 ; echo 'timeout'; kill $pidApp ) &
killerPid=$!

wait $pidApp
status=$?
(kill -0 $killerPid && kill $killerPid) || true

exit $status

退出状态 143 表示 SIGTERM,几乎肯定是由于我们的超时。

解决方案 4:

app1 &
app2 &
sleep 60 &

wait -n

解决方案 5:

出于我个人的观点,我们可以将 Teixeira 的解决方案归结为:

try_wait() {
    # Usage: [PID]...
    for ((i = 0; i < $#; i += 1)); do
        kill -0 $@ && sleep 0.001 || return 0
    done
    return 1 # timeout or no PIDs
} &>/dev/null

Bashsleep接受秒的小数部分,0.001s = 1 ms = 1 KHz = 足够的时间。但是,UNIX 在文件和进程方面没有漏洞。try_wait完成得很少。

$ cat &
[1] 16574
$ try_wait %1 && echo 'exited' || echo 'timeout'
timeout
$ kill %1
$ try_wait %1 && echo 'exited' || echo 'timeout'
exited

我们必须回答一些难题才能取得进一步的进展。

为什么没有wait超时参数?可能是因为、、和timeout命令kill -0可以更精确地告诉机器我们想要什么。wait`wait -n`

为什么它wait首先内置于 Bash,所以它timeout wait PID不起作用?也许只是为了让 Bash 能够实现正确的信号处理。

考虑:

$ timeout 30s cat &
[1] 6680
$ jobs
[1]+    Running   timeout 30s cat &
$ kill -0 %1 && echo 'running'
running
$ # now meditate a bit and then...
$ kill -0 %1 && echo 'running' || echo 'vanished'
bash: kill: (NNN) - No such process
vanished

无论是在物质世界还是在机器中,我们都需要一些地面来奔跑,我们也需要一些地面来等待。

  • kill失败时,您几乎不知道原因。除非您编写了该过程,或者其手册中指明了具体情况,否则没有办法确定合理的超时值。

  • try_wait编写完该过程后,您可以实现适当的 TERM 处理程序,甚至可以响应通过命名管道发送给它的“Auf Wiedersehen!”。然后,您甚至可以使用类似:-) 的咒语

解决方案 6:

我编写了一个 bash 函数,它将等待直到 PID 完成或超时,如果超过超时则返回非零并打印所有未完成的 PID。

function wait_timeout {
  local limit=${@:1:1}
  local pids=${@:2}
  local count=0
  while true
  do
    local have_to_wait=false
    for pid in ${pids}; do
      if kill -0 ${pid} &>/dev/null; then
        have_to_wait=true
      else
        pids=`echo ${pids} | sed -e "s/^${pid}$//g"`
      fi
    done
    if ${have_to_wait} && (( $count < $limit )); then
      count=$(( count + 1 ))
      sleep 1
    else
      echo ${pids}
      return 1
    fi
  done   
  return 0
}

要使用这个只是wait_timeout $timeout $PID1 $PID2 ...

解决方案 7:

您可以使用“读取”内部命令的超时。

下面的操作将终止未终止的作业并在最多 60 秒后显示已完成作业的名称:

( (job1; echo -n "job1 ")& (job2; echo -n "job2 ")&) | (read -t 60 -a jobarr; echo ${jobarr[*]} ${#jobarr[*]} )

它的工作原理是创建一个包含所有后台作业的子 shell。此子 shell 的输出被读入 bash 数组变量,可根据需要使用该变量(在此示例中,通过打印数组 + 元素计数)。

确保在与读取命令相同的子 shell 中引用 ${jobarr}(因此带有括号),否则 ${jobarr} 将为空。

读取命令终止后,所有子 shell 将自动静音(而不是被杀死)。您必须自行杀死它们。

解决方案 8:

又一次超时 狂欢的剧本

运行多个子进程,总超时。使用最近的狂欢功能,我写了这个:

#!/bin/bash
maxTime=5.0 jobs=() pids=() cnt=1 Started=${EPOCHREALTIME/.}
if [[ $1 == -m ]] ;then maxTime=$2; shift 2; fi

for cmd ;do  # $cmd is unquoted in order to use strings as command + args
    $cmd &
    jobs[$!]=$cnt pids[cnt++]=$!
done

printf -v endTime %.6f $maxTime
endTime=$(( Started + 10#${endTime/.} ))
exec {pio}<> <(:) # Pseudo FD for "builtin sleep" by using "read -t" 
while ((${#jobs[@]})) && (( ${EPOCHREALTIME/.} < endTime ));do
    for cnt in ${jobs[@]};do
        if ! jobs $cnt &>/dev/null;then
            Elap=00000$(( ${EPOCHREALTIME/.} - Started ))
            printf 'Job %d (%d) ended after %.4f secs.
' \n                   $cnt ${pids[cnt]} ${Elap::-6}.${Elap: -6}
            unset jobs[${pids[cnt]}] pids[cnt]
        fi
    done
    read -ru $pio -t .02 _
done
if ((${#jobs[@]})) ;then
    Elap=00000$(( ${EPOCHREALTIME/.} - Started ))
    for cnt in ${jobs[@]};do
        printf 'Job %d (%d) killed after %.4f secs.
' \n               $cnt ${pids[cnt]} ${Elap::-6}.${Elap: -6}
    done
    kill ${pids[@]}
fi

示例运行:

  • 带参数的命令可以作为字符串提交

  • -m开关让您选择一个浮点数作为以秒为单位的最大时间

$ ./execTimeout.sh -m 2.3 "sleep 1" 'sleep 2' sleep {3,4}  'cat /dev/tty'
Job 1 (460668) ended after 1.0223 secs.
Job 2 (460669) ended after 2.0424 secs.
Job 3 (460670) killed after 2.3100 secs.
Job 4 (460671) killed after 2.3100 secs.
Job 5 (460672) killed after 2.3100 secs.

为了测试这一点,我编写了这个脚本

  • 选择介于1.00009.9999秒之间的随机持续时间

  • 输出0和之间的随机行数8。(它们无法输出任何内容)。

  • 行输出包含进程 ID($$)、剩余要打印的行数和总持续时间(秒)。

#!/bin/bash

tslp=$RANDOM lnes=${RANDOM: -1}
printf -v tslp %.6f ${tslp::1}.${tslp:1}
slp=00$((${tslp/.}/($lnes?$lnes:1)))
printf -v slp %.6f ${slp::-6}.${slp: -6}
# echo >&2 Slp $lnes x $slp == $tslp
exec {dummy}<> <(: -O)
while read -rt $slp -u $dummy; ((--lnes>0)); do
    echo $$ $lnes $tslp
done

一次运行此脚本 5 次,超时时间为 5.0 秒:

$ ./execTimeout.sh -m 5.0 ./tstscript.sh{,,,,}
2869814 6 2.416700
2869815 5 3.645000
2869814 5 2.416700
2869814 4 2.416700
2869815 4 3.645000
2869814 3 2.416700
2869813 5 8.414000
2869812 1 3.408000
2869814 2 2.416700
2869815 3 3.645000
2869814 1 2.416700
2869815 2 3.645000
Job 3 (2869814) ended after 2.4511 secs.
2869813 4 8.414000
2869815 1 3.645000
Job 1 (2869812) ended after 3.4518 secs.
Job 4 (2869815) ended after 3.6757 secs.
2869813 3 8.414000
Job 2 (2869813) killed after 5.0159 secs.
Job 5 (2869816) killed after 5.0159 secs.

解决方案 9:

有些进程在超时调用时无法正常工作。我遇到了一个问题,需要在 qemu 实例周围放置一个超时捕获,如果你调用

timeout 900 qemu 

它将永远挂起。

我的解决方案

./qemu_cmd &
qemuPid=$!
timeout 900 tail --pid=$qemuPid -f /dev/null
ret=$?
if [ "$ret" != "0" ]; then
   allpids=()
   descendent_pids $tracePid
   for pids in ${allpids[@]};do
      kill -9 $pids
   done
fi

descendent_pids(){
   allpids=("${allpids[@]}" $1)
   pids=$(pgrep -P $1)
   for pid in $pids; do
      descendent_pids $pid
   done
}

还要注意的是,超时并不总是会终止后代进程,这取决于您从超时中产生的 cmd 的复杂程度。

解决方案 10:

迟做总比不做好,解决方案是使用等待而不是轮询(虽然仍然是一个循环),但仍会尽快停止。

app1 &
pidApp1=$!
app2 &
pidApp2=$!

# timeout 60 wait $pidApp1 $pidApp2
declare -A pidApps=( [$pidApp1]=running [$pidApp2]=running )
{ sleep 60; echo "stop"; } | read &
pidTmout=$!
while [[ ${#pidApps[@]} -gt 0 ]]; do
    wait -np pidStop
    [[ $pidStop == $pidTmout ]] && break
    unset pidApps[$pidStop]
done
[[ ${pidApps[$pidApp1]} == running ]] && kill -9 $pidApp1
[[ ${pidApps[$pidApp2]} == running ]] && kill -9 $pidApp2

解决方案 11:

使用wait -parg,您可以等待多个进程并确定哪个进程先完成。通过让其中一个进程超时,您可以知道是否发生了超时或成功。

这确实有点奇怪,但可以让你更明确地处理错误。如果你需要这种级别的控制,最好转向更完善的语言,比如 Python。

就像上面的答案所说的那样,timeout对于 99% 的简单情况来说,使用可能就足够了。当需要一个更丰富的看门狗进程来观察心跳而不是简单的超时时,我想出了下面的解决方案的部分内容。不要把它看作是一个完整的替代品,更像是从中复制一些片段的东西。

#!/bin/bash

# Start 2 processes to wait for, for sake of example, use sleep and let one of them fail
remaining=()
sleep 1 && false &
remaining+=($!)
sleep 2 && true &
remaining+=($!)

# Start the watchdog process, for sake of example, use sleep as a fixed timeout
sleep 2 &
pid_timeout=$!


# Keep looping until no more processes left
while [ ${#remaining[@]} != 0 ]
do
    echo "Waiting for remaining processes ${remaining[*]}"
    wait -n -p firstFinished "${remaining[@]}" $pid_timeout
    first_exit_code=$?
    
    if [ $firstFinished = $pid_timeout ]
    then
        echo "Timeout, killing remaining processes ${remaining[*]}"
        kill "${remaining[@]}"
        exit 124
    else 
        # Remove the finished process from remaining list
        echo "Process $firstFinished finished with code $first_exit_code"
        
        new_array=()
        for rem_pid in "${remaining[@]}"
        do
            if [ "$rem_pid" = "$firstFinished" ] 
            then
                echo "Process $rem_pid reported finished by wait, remove from list"
                continue
            fi 

            if ! kill -0 "$rem_pid" 
            then
                echo "Process $rem_pid no longer running, removing from wait list"
                # potential race condition when multiple processes finish before next iteration of loop
                # TODO: How to get exit code?
                continue
            fi 

            new_array+=("$rem_pid")
        done
        remaining=("${new_array[@]}")
        unset new_array
    fi
done
kill $pid_timeout || true
echo "all done"
相关推荐
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   601  
  华为IPD与传统研发模式的8大差异在快速变化的商业环境中,产品研发模式的选择直接决定了企业的市场响应速度和竞争力。华为作为全球领先的通信技术解决方案供应商,其成功在很大程度上得益于对产品研发模式的持续创新。华为引入并深度定制的集成产品开发(IPD)体系,相较于传统的研发模式,展现出了显著的差异和优势。本文将详细探讨华为...
IPD流程是谁发明的   7  
  如何通过IPD流程缩短产品上市时间?在快速变化的市场环境中,产品上市时间成为企业竞争力的关键因素之一。集成产品开发(IPD, Integrated Product Development)作为一种先进的产品研发管理方法,通过其结构化的流程设计和跨部门协作机制,显著缩短了产品上市时间,提高了市场响应速度。本文将深入探讨如...
华为IPD流程   9  
  在项目管理领域,IPD(Integrated Product Development,集成产品开发)流程图是连接创意、设计与市场成功的桥梁。它不仅是一个视觉工具,更是一种战略思维方式的体现,帮助团队高效协同,确保产品按时、按质、按量推向市场。尽管IPD流程图可能初看之下显得错综复杂,但只需掌握几个关键点,你便能轻松驾驭...
IPD开发流程管理   8  
  在项目管理领域,集成产品开发(IPD)流程被视为提升产品上市速度、增强团队协作与创新能力的重要工具。然而,尽管IPD流程拥有诸多优势,其实施过程中仍可能遭遇多种挑战,导致项目失败。本文旨在深入探讨八个常见的IPD流程失败原因,并提出相应的解决方法,以帮助项目管理者规避风险,确保项目成功。缺乏明确的项目目标与战略对齐IP...
IPD流程图   8  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用