Bash

執行前台程序,直到後台程序退出 shell

  • September 8, 2021

我在-daemonize模式下執行 QEMU 虛擬機,然後生成一個任意前台(可能是互動式)程序,用於與 QEMU 實例進行互動。通常,一旦前台程序完成,我會通過 pidfile 清理 QEMU 實例:

qemu-system ... -pidfile ./qemu.pid -daemonize

/my/custom/interactive/process

pkill -F ./qemu.pid

但是在某些情況下,QEMU 可以獨立退出並且我的前台程序繼續執行。但我想阻止它以防萬一。所以我的自定義互動過程的行為應該是這樣的:

tail -f --pid=./qemu.pid /dev/null

我怎樣才能很好地做到這一點?也許有某種類似超時的包裝器,所以我可以執行類似的東西:

trackpid ./qemu.pid /my/custom/interactive/process

最後我得到了以下程式碼:

qemu-system ... -pidfile ./qemu.pid -daemonize

{
 tail -f --pidfile="$(cat ./qemu.pid)" /dev/null
 kill -INT 0
} &

/my/custom/interactive/process
kill $!

pkill -F ./qemu.pid

花括號中的腳本實際上是在後台執行的 pidfile 監視器。一旦 pid 消失,監控終止目前程序組。我使用kill -INT 0它是因為它為我提供了最可靠和最乾淨的結果。

其他選擇是:

kill -- 0(使用 TERM 信號終止,不會正確終止互動過程)

kill -INT $$(僅殺死 shell 程序,不會正確終止互動程序)

kill -- -$$(殺死由 shell 的 pid 表示的程序組,並不總是正常工作,我假設由於呼叫sudo作為程序組負責人)

pkill -P $$(僅殺死子程序,實際上可以,但我更喜歡使用內置的 shell 並依賴 Ctrl-C 處理行為)。

另一點是,如果我的互動過程已自行完成,我必須終止監視程序以避免進一步推斷退出和清理腳本。

您可以輪詢 qemu 程序是否消失,並在它消失時提前終止。這是經過快速測試的程式碼,尤其是未使用qemu-system.

它也有很多。您可以刪除這些#DEBUG行,但如果您有興趣了解它們是如何連接在一起的,請取消註釋並將程序輸出與程式碼進行比較。

#!/bin/bash

InvokeQemu()
{
   local i pid pidFile=qemu.pid

   # Start the qemu process, and return the PID if possible
   #
   (
       # qemu-system ... -pidFile "$pidFile" -daemonize
       ( sleep 30 & sleep 0.5 && echo $! >"$pidFile" )    # FAKE IT for half a minute
   ) >/dev/null 2>&1 </dev/null

   #echo "InvokeQemu: checking for successful daemonisation" >&2    #DEBUG
   for i in 1 2 3
   do
       # Does the PID file exist yet
       #echo "InvokeQemu: attempt $i" >&2    #DEBUG
       if [[ -s "$pidFile" ]] && pid=$(cat "$pidFile") && [[ -n "$pid" ]]
       then
           printf "%s\n" $pid
           #echo "InvokeQemu: pid=$pid" >&2    #DEBUG
           return 0
       fi

       # Pause a moment or so before trying again
       sleep 2
   done
   return 1
}

MonitorPIDs()
{
   local pid

   for pid in "$@"
   do
       #echo "MonitorPIDs: checking pid $pid" >&2    #DEBUG
       if err=$(kill -0 "$pid" 2>&1) || [[ "$err" == *permitted* || "$err" == *denied* ]]
       then
           # Process still exists
           :
           #echo "MonitorPIDs: pid $pid still alive" >&2    #DEBUG
       else
           #echo "MonitorPIDs: pid $pid has died" >&2    #DEBUG
           echo "$pid"
           return 1
       fi
   done
   #echo "MonitorPIDs: all good" >&2    #DEBUG
   return 0
}

########################################################################
# Go
myPid=$$

# Start the qemu emulator
echo "Starting qemu emulator"
qemuPid=$(InvokeQemu)

if [[ -z "$qemuPid" ]]
then
   echo "Could not start qemu" >&2
   exit 1
fi

# Start the monitor process
#
# Once any of them is no longer running it will fire SIGTERM to its
# remaining PIDs and then exit
echo "Starting monitor process"
(
   while MonitorPIDs $qemuPid $myPid >/dev/null
   do
       #echo "(Monitor): all good" >&2    #DEBUG
       sleep 2
   done
   kill $qemuPid $myPid 2>/dev/null
) &

# Start your interactive foreground process
#
# You will receive SIGTERM within a few seconds of the emulator exiting,
# so you may want to trap that
echo "Starting interactive process"
while read -p "What do you want to do? " x
do
   echo "OK"
   sleep 1
done
exit 0

引用自:https://unix.stackexchange.com/questions/668024