ssh遠程執行nohup命令不退出


 

 

Linux系統下,使用默認用戶root。遠程target機器的主目錄下有個腳本test.sh,可執行權限,內容只有一條命令:sleep 10

在本地機器上執行 ssh target "nohup ./test.sh &",結果ssh不立即退出,等test.sh執行完畢之后才退出。一般我們使用nohup命令是為了在斷開到某個服務器的ssh連接之后,之前執行的命令仍然正常地在服務器運行。但是前面的現象其實與nohup命令沒有什么關系,只是ssh本身的問題;nohup其作用的前提是用戶使用ssh登錄到服務器上。至於跟nohup扯上關系,我猜是因為在大家的印象中上面這種nohup命令的執行方式應該是立即退出的,結果反差太大,所以當作了一個特別問題。相關的說明可以參見:

https://en.wikipedia.org/wiki/Nohup
http://www.snailbook.com/faq/background-jobs.auto.html

http://www.openssh.com/faq.html#3.10
https://bugzilla.mindrot.org/show_bug.cgi?id=52

 

解決的方法是,手動在命令里面指定重定向,即上面的命令換成:ssh target "nohup ./test.sh >/dev/null 2>&1 &",然后就OK。下面的分析表明了nohup命令與“ssh host "cmd"”方式的ssh命令沒有任何關系(因為這種方式不會涉及SIGHUP),所以換成ssh target "./test.sh >/dev/null 2>&1 &" 就可以了。

分析:

  一般處理ssh遠程執行某個命令的任務,在遠程目標機器上先建立一個sshd的子進程(父進程是最初始的sshd),然后由這個sshd進程啟動一個bash進程(如果使用bash進程)來執行傳遞過來的命令。  針對這次任務建立的sshd進程和bash進程在文件描述符方面有一定關系:通常bash進程的0 1 2三個文件描述符通過管道與sshd的相應文件描述符聯系起來。這可以通過查找建立的sshd進程和bash進程在/proc文件系統下的相應進程的fd目錄的詳細情況。ssh遠程執行命令這種建立ssh連接的方式在ps -ef 中顯示的sshd進程是有"sshd root@notty"標記。此sshd進程的命令可以通過命令“ps -ef | grep -v grep | grep 'sshd.*notty' | awk '{print $2}'”得到,而相關bash進程的PID可用$$獲取。遠程執行下面命令可以一步到位,得到比較結果:

  ssh target "TMPSPID=\$(ps -ef | grep -v grep | grep  -e 'sshd.*notty' | awk '{print \$2}');echo \$TMPSPID;ls -l /proc/\$TMPSPID/fd;echo \$\$;ls -l /proc/\$\$/fd" 

  如果遠程執行的命令是后台執行,那么可以發現新啟動的bash進程的父進程成了1,而輸入即描述符0重定向到了/dev/null。 nohup是防止進程被SIGHUP信號中斷,正常使用的時候也會進行一些重定向操作,即當標准輸入/輸出/錯誤等是終端的時候,會對它們進行重定向。但是ssh遠程執行命令時,這些條件都不滿足,因為文件描述符0,1,2(正常情況下)都被重定向到管道了。所以遠程執行nohup時不會進行相關重定向操作。而當遠程執行后台命令的時候,雖然標准輸入被重定向到了/dev/null,但是標准輸出和錯誤還是管道, 所以針對這次任務啟動的sshd進程還不會結束。所以執行遠程命令時,還必須自己在命令行上重定向標准輸出和標准錯誤才行。

對於上面的test.sh腳本,下面給出幾種命令執行執行方式:
  ssh target "./test.sh"           # 等待命令完成后退出;本地Ctrl+C中斷ssh會話,不會中斷test.sh的執行(bash父進程變為1)(與登錄終端執行命令而終端連接斷開時的行為不一樣)
  ssh target "./test.sh &"        # 等待命令完成后退出;本地Ctrl+C中斷ssh會話,不會中斷test.sh的執行(bash父進程本來就為1)
  ssh target "nohup ./test.sh &"  # 等待命令完成后退出;本地Ctrl+C中斷ssh會話,不會中斷test.sh的執行(bash父進程本來就為1)
  ssh target "nohup ./test.sh >/dev/null 2>&1 &"  # 啟動test.sh執行后就會退出(bash父進程本來就為1)
 ssh target "./test.sh >/dev/null 2>&1 &"              # 啟動test.sh執行后就會退出(bash父進程本來就為1),這也表明ssh不退出與nohup命令本身沒有什么關系

實際上如先ssh登錄target,執行./test.sh &,然后正常退出ssh(即exit命令),那么./test.sh這個腳本也不會終止,而且會將父進程換成1;如果不正常退出,而是直接關閉連接,那么會導致./test.sh任務終止。

 

 

補充:

感覺上面的分析還不是很到位,因為簡單命令還不能夠顯示出真實情況,比如執行

ssh target "./test.sh"

在遠程機器上執行“ps -ef | grep 'test\|notty'”命令,結果如下

root     35929  3306  0 19:20 ?        00:00:00 sshd: root@notty
root     35931 35929  0 19:20 ?        00:00:00 /bin/bash ./test.sh”

好像執行./test.sh的bash進程直接由顯示的sshd進程創建,其實情況應該不是這樣的。先執行一個稍復雜的命令:

ssh target "for w in a b c; do ./test.sh; done"

同樣使用上面的查看命令可以看到如下結果:

root     36219  3306  0 19:29 ?        00:00:00 sshd: root@notty
root     36221 36219  0 19:29 ?        00:00:00 bash -c for w in a b c; do ./test.sh; done
root     36228 36221  0 19:29 ?        00:00:00 /bin/bash ./test.sh

這就表明了其實有兩層進程關系,sshd ---- bash -c ----- bash,即sshd 先創建一個bash以bash -c的方式執行傳遞過來的作為命令的字符串,然后再由這個bash創建執行./test.sh腳本的子bash進程(這個可以創建多個)。而本地執行ssh host "cmd"形式命令要能迅速返回,必須滿足的條件是:該命令對象的sshd進程(一般是sshd: root@notty),沒有子進程需要等待結束(靠將第一個bash搞成后台進程,或者第一個bash會立即執行完命令自然退出——即它啟動一些后台子進程), 而且沒有其他進程與它有管道連接關系(靠重定向解決,在第一個bash處或者所有第二層bash處都可以)。簡而言之,要ssh host "cmd"形式命令立即返回,在整個命令最后面添加“>/dev/null 2>&1 &”,是有保證的。注意,對於組合的命令, 可能需要放到{}中才行,比如“{ cmd; } >/dev/null 2>&1 &”這樣的形式。這是因為重定向只對單個簡單命令或單個復合命令有效。

下面通過一些實際例子的情況幫助大家認識(/dev/null也可以是某個本地文件):

 

ssh target "for w in a b c; do ./test.sh >/dev/null 2>&1 0</dev/null; done"

ssh不返回,./test.sh一個一個啟動

ssh target "for w in a b c; do ./test.sh >/dev/null 2>&1 0</dev/null; done &"

ssh 不返回,./test.sh一個一個啟動。 第一個bash(由sshd啟動的bash -c)是后台執行的,但是文件描述符1和2還與sshd有管道連接,所以不返回

ssh target "for w in a b c; do ./test.sh >/dev/null 2>&1 0</dev/null; done >/dev/null 2>&1 &"

ssh立即返回,./tesh.sh一個接一個啟動

ssh target "for w in a b c; do ./test.sh ; done >/dev/null 2>&1 &"

ssh立即返回,./test.sh一個接一個啟動;由於執行./test.sh的bash是由第一個bash啟動的,而第一個bash執行了重定向,所以該bash也繼承了這些重定向,換言之這條命令與上條命令的效果一樣,即內層的./test.sh無需重定向了

ssh target "for w in a b c; do ./test.sh & done >/dev/null 2>&1 &"

ssh立即退出,./test.sh全部啟動,第一個bash也退出了

ssh target "for w in a b c; do ./test.sh & done >/dev/null 2>&1"

ssh立即退出(為什么?因為第一個bash啟動三個./tesh.sh的bash進程后,退出了;而執行./test.sh的bash進程因為繼承了父bash的文件描述符,所以沒有管道與sshd連接,因此ssh退出

ssh target "for w in a b c; do ./test.sh & done "

ssh不返回,因為執行./test.sh與sshd還有管道連接

ssh target "for w in a b c; do ./test.sh >/dev/null 2>&1 0</dev/null & done" 

ssh返回,因為第一個bash啟動三個子bash之后結束,而子bash與sshd之間又沒有管道上連接

ssh target "for w in a b c; do ./test.sh >/dev/null 2>&1 0</dev/null & done &"

同上,因為第一個bash啟動所有子進程后會退出,此時將第一個bash作為后台進程已經意義不大

 

ssh target "./test.sh && ./test.sh >/dev/null 2>&1 &"

ssh不會返回

ssh target "{ ./test.sh && ./test.sh; } >/dev/null 2>&1 &" 

ssh會返回


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM