2009-10-15 45 views
86

我有一個命令CMD從我的主Bourne shell腳本中調用,需要永久使用。shell - 獲取後臺進程的退出代碼

我想修改腳本如下:

  1. 並行運行CMD命令作爲後臺進程($ CMD &)。
  2. 在主腳本中,每隔幾秒就有一個循環來監視生成的命令。該循環還將一些消息回顯給stdout,指示腳本的進度。
  3. 生成的命令終止時退出循環。
  4. 捕獲並報告產生的進程的退出代碼。

有人能給我指針來完成這個嗎?

+1

...而獲勝者是? – TrueY 2016-06-07 21:19:43

回答

7
#/bin/bash 

#pgm to monitor 
tail -f /var/log/messages >> /tmp/log& 
# background cmd pid 
pid=$! 
# loop to monitor running background cmd 
while : 
do 
    ps ax | grep $pid | grep -v grep 
    ret=$? 
    if test "$ret" != "0" 
    then 
     echo "Monitored pid ended" 
     break 
    fi 
    sleep 5 

done 

wait $pid 
echo $? 
+2

這是避免'grep -v'的一個技巧。您可以將搜索限制在行首:'grep'^'$ pid'另外,無論如何,您都可以執行'ps p $ pid -o pid ='。另外,'tail -f'不會結束,除非你殺了它,所以我認爲這不是一個很好的演示方法(至少沒有指出)。您可能希望將您的'ps'命令的輸出重定向到'/ dev/null',否則它會在每次迭代時進入屏幕。你的'exit'會導致'wait'被跳過 - 它應該是一個'break'。但是不是'while' /'ps'和'wait'冗餘嗎? – 2009-10-15 06:40:00

+5

爲什麼每個人都忘記'kill -0 $ pid'?它實際上並沒有發送任何信號,只使用內置的shell而不是外部進程來檢查進程是否活着。 – ephemient 2009-10-17 00:17:01

+1

因爲你只能殺死你擁有的進程:'bash:kill:(1) - 不允許的操作' – 2013-05-02 03:16:41

89

1:在bash中,$!包含執行的最後一個後臺進程的PID。無論如何,這將告訴你要監控的過程。

4:wait <n>一直等到具有ID的進程完成(它會阻塞,直到進程完成,因此您可能不想調用此過程直到確定進程已完成)。在wait返回後,過程的退出代碼返回變量$?

2,3:psps | grep " $! "可以告訴您進程是否仍在運行。這取決於你如何理解輸出並決定完成的程度。 (ps | grep不是白癡的證據,如果你有時間可以想出一個更強大的方法來判斷這個過程是否仍在運行)。

這裏是一個骨架腳本:

# simulate a long process that will have an identifiable exit code 
(sleep 15 ; /bin/false) & 
my_pid=$! 

while ps | grep " $my_pid "  # might also need | grep -v grep here 
do 
    echo $my_pid is still in the ps output. Must still be running. 
    sleep 3 
done 

echo Oh, it looks like the process is done. 
wait $my_pid 
my_status=$? 
echo The exit status of the process was $my_status 
+10

的需求#2'ps -p $ my_pid -o pid ='既不需要grep也不需要。 – 2009-10-15 06:43:01

+1

@丹尼威廉姆森'ps'有很多口味。你的電話不適合我,但是'ps -p $ my_pid'。你的大點'grep'沒有必要是正確的。 – mob 2009-10-15 16:01:10

+0

嗯..其實我找不出一個避免greg在Cygwin上的好方法。無論$ pid是否存在,'ps -p $ pid'的退出狀態總是爲0。我可以說'while'['ps -p $ pid |') wc -l'\> 1]'但這並不是什麼改進...... – mob 2009-10-15 17:06:06

4

我會稍微改變你的方法。如果命令仍然存在且報告消息,則不要每隔幾秒檢查一次,還要讓另一個進程每隔幾秒報告一次該命令仍在運行,然後在命令完成時終止該進程。例如:

 
#!/bin/sh 

cmd() { sleep 5; exit 24; } 

cmd & # Run the long running process 
pid=$! # Record the pid 

# Spawn a process that coninually reports that the command is still running 
while echo "$(date): $pid is still running"; do sleep 1; done & 
echoer=$! 

# Set a trap to kill the reporter when the process finishes 
trap 'kill $echoer' 0 

# Wait for the process to finish 
if wait $pid; then 
    echo "cmd succeeded" 
else 
    echo "cmd FAILED!! (returned $?)" 
fi 
2

一個簡單的例子,類似於上面的解決方案。這不需要監視任何過程輸出。下一個示例使用tail來跟蹤輸出。

$ echo '#!/bin/bash' > tmp.sh 
$ echo 'sleep 30; exit 5' >> tmp.sh 
$ chmod +x tmp.sh 
$ ./tmp.sh & 
[1] 7454 
$ pid=$! 
$ wait $pid 
[1]+ Exit 5     ./tmp.sh 
$ echo $? 
5 

使用尾部跟隨過程輸出,當該過程完成後退出。

$ echo '#!/bin/bash' > tmp.sh 
$ echo 'i=0; while let "$i < 10"; do sleep 5; echo "$i"; let i=$i+1; done; exit 5;' >> tmp.sh 
$ chmod +x tmp.sh 
$ ./tmp.sh 
0 
1 
2 
^C 
$ ./tmp.sh > /tmp/tmp.log 2>&1 & 
[1] 7673 
$ pid=$! 
$ tail -f --pid $pid /tmp/tmp.log 
0 
1 
2 
3 
4 
5 
6 
7 
8 
9 
[1]+ Exit 5     ./tmp.sh > /tmp/tmp.log 2>&1 
$ wait $pid 
$ echo $? 
5 
0

這可能超出你的問題,但是如果你擔心的進程運行時間的長短,你可能有興趣在一個時間間隔後,檢查正在運行的後臺進程的狀態。這是很容易檢查哪些孩子的PID使用pgrep -P $$仍在運行,但是,我想出了以下解決方案來檢查已經過期的PID的退出狀態:

cmd1() { sleep 5; exit 24; } 
cmd2() { sleep 10; exit 0; } 

pids=() 
cmd1 & pids+=("$!") 
cmd2 & pids+=("$!") 

lasttimeout=0 
for timeout in 2 7 11; do 
    echo -n "interval-$timeout: " 
    sleep $((timeout-lasttimeout)) 

    # you can only wait on a pid once 
    remainingpids=() 
    for pid in ${pids[*]}; do 
    if ! ps -p $pid >/dev/null ; then 
     wait $pid 
     echo -n "pid-$pid:exited($?); " 
    else 
     echo -n "pid-$pid:running; " 
     remainingpids+=("$pid") 
    fi 
    done 
    pids=(${remainingpids[*]}) 

    lasttimeout=$timeout 
    echo 
done 

,輸出:

interval-2: pid-28083:running; pid-28084:running; 
interval-7: pid-28083:exited(24); pid-28084:running; 
interval-11: pid-28084:exited(0); 

注意:如果您願意,可以將$pids更改爲字符串變量而非數組以簡化操作。

1

另一種解決方案是通過proc文件系統監視進程(比ps/grep組合安全);當你啓動一個進程它在/ proc/$ PID相應的文件夾,因此該解決方案可能是

#!/bin/bash 
.... 
doSomething & 
local pid=$! 
while [ -d /proc/$pid ]; do # While directory exists, the process is running 
    doSomethingElse 
    .... 
else # when directory is removed from /proc, process has ended 
    wait $pid 
    local exit_status=$? 
done 
.... 

現在你可以使用$ EXIT_STATUS變量,只要你喜歡。

+0

在bash中不起作用? ''語法錯誤:「其他」意外(期待「完成」)' – benjaoming 2016-02-10 11:21:40

6

正如我所看到的幾乎所有答案都使用外部實用程序(主要是ps)來輪詢後臺進程的狀態。還有一個更加unixesh的解決方案,捕獲SIGCHLD信號。在信號處理程序中,必須檢查哪個子進程已停止。它可以通過內置的(通用的)kill -0 <PID>或者檢查/proc/<PID>目錄(Linux專用)的存在或使用jobs內置的(特定。jobs -l也可以報告pid。在這種情況下,輸出的第3個字段可以被停止|正在運行|完成|退出)。

這是我的例子。

啓動的過程被稱爲loop.sh。它接受-x或一個數字作爲參數。對於-x將以退出代碼1退出。對於一個數字,它將等待num * 5秒。每5秒鐘打印一次PID。

啓動過程被稱爲launch.sh

#!/bin/bash 

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

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 

更多解釋見:Starting a process from bash script failed

+0

因爲我們在談論Bash,for循環可能寫成:'for i in $ {!pids [@]};'使用參數擴展。 – PlasmaBinturong 2018-02-22 10:19:53

31

這是我如何解決它的時候我也有類似的需要:

# Some function that takes a long time to process 
longprocess() { 
     # Sleep up to 14 seconds 
     sleep $((RANDOM % 15)) 
     # Randomly exit with 0 or 1 
     exit $((RANDOM % 2)) 
} 

pids="" 
# Run five concurrent processes 
for i in {1..5}; do 
     (longprocess) & 
     # store PID of process 
     pids+=" $!" 
done 

# Wait for all processes to finnish, will take max 14s 
for p in $pids; do 
     if wait $p; then 
       echo "Process $p success" 
     else 
       echo "Process $p fail" 
     fi 
done 
+0

我喜歡這種方法。 – 2017-07-06 17:43:28

+0

謝謝!這在我看來是最簡單的方法。 – 2017-09-26 20:20:19

+0

解決問題的一個非常好的方法! – 2017-11-30 13:53:50

0

有了這個方法,你的腳本不需要等待後臺進程,你只需要監視一個臨時文件的退出狀態。

FUNCmyCmd() { sleep 3;return 6; }; 

export retFile=$(mktemp); 
FUNCexecAndWait() { FUNCmyCmd;echo $? >$retFile; }; 
FUNCexecAndWait& 

現在,你的腳本可以做任何事情,而你只需要繼續監測retFile的內容(也可以包含類似的退出時間,你想要的任何其他信息)。

PS:順便說一句,我編寫的思維在bash

2

一轉到後臺子進程的PID存儲在$!。 您可以將所有子進程的pid存儲到一個數組中,例如PIDS []

wait [-n] [jobspec or pid …] 

等待,直到每個進程ID PID或作業規範JOBSPEC退出並返回退出狀態的最後一個命令等待指定的子進程。如果給出了工作規範,則等待作業中的所有進程。如果沒有給出參數,則等待所有當前活動的子進程,並且返回狀態爲零。如果提供-n選項,則等待等待任何作業終止並返回其退出狀態。如果jobspec和pid都沒有指定外殼的活動子進程,則返回狀態爲127.

使用等待命令您可以等待所有子進程完成,同時您可以獲取每個子進程和存儲的退出狀態狀態變成STATUS []。然後你可以根據狀態做一些事情。

我試過下面的代碼,它運行良好。

#!/bin/bash 

# start 3 child processes concurrently, and store each pid into PIDS[]. 
i=0 
process=(a.sh b.sh c.sh) 
for app in ${process[@]}; do 
    ./${app} & 
    pid=$! 
    PIDS[$i]=${pid} 
    ((i+=1)) 
done 

# wait for all processes to finish, and store each process's exit code into STATUS[]. 
i=0 
for pid in ${PIDS[@]}; do 
    echo "pid=${pid}" 
    wait ${pid} 
    STATUS[$i]=$? 
    ((i+=1)) 
done 

# after all processed finish, check their exit codes in STATUS[]. 
i=0 
for st in ${STATUS[@]}; do 
    if [[ ${st} -ne 0 ]]; then 
    echo "failed" 
    else 
    echo "finish" 
    fi 
    ((i+=1)) 
done 
+0

我試過並證明它運行良好。你可以在代碼中閱讀我的解釋。 – 2017-09-14 07:44:24

+0

請閱讀「我如何寫一個好的答案?」(https://stackoverflow.com/help/how-to-answer)「,您將在其中找到以下信息:** ...嘗試提及任何您的答案中的限制,假設或簡化。簡潔是可以接受的,但更全面的解釋是更好的。**你的回答是可以接受的,但如果你能詳細說明問題和解決方案,你有更好的機會獲得提升。 :-) – 2017-09-14 08:05:34

1

我們的團隊與遠程SSH執行的腳本有相同的需求,該腳本在靜止25分鐘後超時。這是監控循環每秒檢查後臺進程的解決方案,但每10分鐘打印一次以抑制不活動超時。

long_running.sh & 
pid=$! 

# Wait on a background job completion. Query status every 10 minutes. 
declare -i elapsed=0 
# `ps -p ${pid}` works on macOS and CentOS. On both OSes `ps ${pid}` works as well. 
while ps -p ${pid} >/dev/null; do 
    sleep 1 
    if ((++elapsed % 600 == 0)); then 
    echo "Waiting for the completion of the main script. $((elapsed/60))m and counting ..." 
    fi 
done 

# Return the exit code of the terminated background process. This works in Bash 4.4 despite what Bash docs say: 
# "If neither jobspec nor pid specifies an active child process of the shell, the return status is 127." 
wait ${pid}