為什麼當 SSH 會話終止時我的 Python 後台進程也會結束?

為什麼當 SSH 會話終止時我的 Python 後台進程也會結束?

我有一個 bash 腳本,它啟動一個 python3 腳本(我們稱之為startup.sh),其關鍵行是:

nohup python3 -u <script> &

當我ssh直接進入並調用這個腳本時,python 腳本在我退出後繼續在後台運行。但是,當我運行這個時:

ssh -i <keyfile> -o StrictHostKeyChecking=no <user>@<hostname> "./startup.sh"

該進程在運行完成後立即結束ssh並關閉會話。

兩者有什麼差別?

編輯:python 腳本正在透過 Bottle 運行 Web 服務。

EDIT2:我也嘗試過建立初始化腳本調用startup.sh並運行ssh -i <keyfile> -o StrictHostKeyChecking=no <user>@<hostname> "sudo service start <servicename>",但得到了相同的行為。

EDIT3:也許是腳本中的其他內容。這是腳本的大部分內容:

chmod 700 ${key_loc}

echo "INFO: Syncing files."
rsync -azP -e "ssh -i ${key_loc} -o StrictHostKeyChecking=no" ${source_client_loc} ${remote_user}@${remote_hostname}:${destination_client_loc}

echo "INFO: Running startup script."
ssh -i ${key_loc} -o StrictHostKeyChecking=no ${remote_user}@${remote_hostname} "cd ${destination_client_loc}; chmod u+x ${ctl_script}; ./${ctl_script} restart"

EDIT4:當我運行最後一行並在最後睡眠時:

ssh -i ${key_loc} -o StrictHostKeyChecking=no ${remote_user}@${remote_hostname} "cd ${destination_client_loc}; chmod u+x ${ctl_script}; ./${ctl_script} restart; sleep 1"

echo "Finished"

它永遠不會到達echo "Finished",我看到了 Bottle 伺服器訊息,這是我以前從未見過的:

Bottle vx.x.x server starting up (using WSGIRefServer())...
Listening on <URL>
Hit Ctrl-C to quit.

如果我手動透過 SSH 登入並自行終止該進程,我會看到「已完成」。

EDIT5:使用 EDIT4,如果我向任何端點發出請求,我會返回一個頁面,但 Bottle 會出錯:

Bottle vx.x.x server starting up (using WSGIRefServer())...
Listening on <URL>
Hit Ctrl-C to quit.


----------------------------------------
Exception happened during processing of request from ('<IP>', 55104)

答案1

我會斷開命令與其標準輸入/輸出和錯誤流的連接:

nohup python3 -u <script> </dev/null >/dev/null 2>&1 &  

ssh需要一個不再有任何輸出且不需要任何更多輸入的指標。將其他內容作為輸入並重定向輸出意味著ssh可以安全退出,因為輸入/輸出不是來自或去往終端。這意味著輸入必須來自其他地方,並且輸出(STDOUT 和 STDERR)應該來自其他地方。

</dev/null部分指定/dev/null為 的輸入<script>。為什麼這裡有用:

將 /dev/null 重定向到 stdin 將為來自該進程的任何讀取呼叫提供立即 EOF。這通常對於從 tty 分離進程很有用(這樣的進程稱為守護程式)。例如,當透過 ssh 遠端啟動後台進程時,必須重定向 stdin 以防止進程等待本地輸入。 https://stackoverflow.com/questions/19955260/what-is-dev-null-in-bash/19955475#19955475

或者,只要當前ssh會話不需要保持開啟狀態,從另一個輸入來源重新導向應該相對安全。

使用該>/dev/null部分,shell 將標準輸出重定向到 /dev/null ,實質上將其丟棄。>/path/to/file也會起作用。

最後一部分2>&1是將 STDERR 重新導向到 STDOUT。

程式有三種標準的輸入和輸出來源。如果它是互動式程序,則標準輸入通常來自鍵盤;如果它正在處理另一個程式的輸出,則標準輸入通常來自另一個程式。程式通常會列印到標準輸出,有時也列印到標準錯誤。這三個檔案描述符(您可以將它們視為「資料管道」)通常稱為 STDIN、STDOUT 和 STDERR。

有時它們沒有被命名,而是被編號!它們的內建編號依序為 0、1 和 2。預設情況下,如果您沒有明確命名或編號第一,那麼您正在談論 STDOUT。

在這種情況下,您可以看到上面的命令將標準輸出重定向到/dev/null,您可以在其中轉儲不需要的任何內容(通常稱為位元桶),然後將標準錯誤重新導向到標準輸出(執行此操作時,必須在目的地前面新增 &)。

因此,簡短的解釋是「該命令的所有輸出都應該被推入黑洞」。這是讓程式真正安靜的好方法!
> /dev/null 2>&1 是什麼意思? | Xaprb

答案2

看著man ssh

 ssh [-1246AaCfgKkMNnqsTtVvXxYy] [-b bind_address] [-c cipher_spec] [-D [bind_address:]port]
     [-e escape_char] [-F configfile] [-I pkcs11] [-i identity_file] [-L [bind_address:]port:host:hostport]
     [-l login_name] [-m mac_spec] [-O ctl_cmd] [-o option] [-p port]
     [-R [bind_address:]port:host:hostport] [-S ctl_path] [-W host:port] [-w local_tun[:remote_tun]]
     [user@]hostname [command]

當您執行時,ssh -i <keyfile> -o StrictHostKeyChecking=no <user>@<hostname> "./startup.sh"您正在將 shell 腳本startup.sh 作為 ssh 命令執行。

從描述來看:

如果指定了 command,則它將在遠端主機上執行,而不是在登入 shell 上執行。

基於此,應該是遠端運行腳本。

它與在本地端運行的區別nohup python3 -u <script> &在於,它作為本地後台進程運行,而 ssh 命令嘗試將其作為遠端後台進程運行。

如果您打算在本機執行該腳本,則不要將startup.sh 作為 ssh 命令的一部分執行。你可以嘗試類似的事情ssh -i <keyfile> -o StrictHostKeyChecking=no <user>@<hostname> && "./startup.sh"

如果您打算遠端執行腳本,並且希望在 ssh 會話終止後繼續此流程,則必須先screen在遠端主機上啟動會話。然後你必須在螢幕中執行 python 腳本,它會在你結束 ssh 會話後繼續運行。

螢幕使用手冊

雖然我認為 screen 是最好的選擇,但如果必須使用 nohup,請考慮shopt -s huponexit在執行 nohup 命令之前在遠端主機上進行設定。或者,您可以使用disown -h [jobID]來標記進程,這樣就不會向它發送 SIGHUP。1

從後台 shell 提示符號退出後如何繼續執行作業?

SIGHUP(掛斷)訊號由系統控制終端或控制進程死亡時使用。您也可以使用 SIGHUP 重新載入設定檔並開啟/關閉日誌檔。換句話說,如果您從終端註銷,所有正在運行的作業都將終止。為了避免這種情況,您可以將 -h 選項傳遞給 disown 命令。此選項標記每個 jobID,以便在 shell 收到 SIGHUP 時不會向作業發送 SIGHUP。

huponexit另外,請參閱有關 shell 退出、終止或刪除時如何運作的摘要。我猜您當前的問題與 shell 會話如何結束有關。2

  1. 只有在設定了 huponexit 選項時,當 ssh 連線關閉時,透過 ssh 連線開啟的 shell 的所有子進程(無論是否處於背景)都會透過 SIGHUP 終止:執行 shopt huponexit 以查看是否屬實。

  2. 如果 huponexit 為 true,則可以使用 nohup 或 disown 將進程與 shell 分離,這樣退出時它就不會被殺死。或者,用螢幕運行東西。

  3. 如果 huponexit 為 false(目前至少在某些 Linux 上這是預設值),那麼後台作業將不會在正常登出時終止。

  4. 但即使 huponexit 為 false,如果 ssh 連線被終止或斷開(與正常註銷不同),那麼後台程序仍然會被終止。這可以透過 (2) 中的 disown 或 nohup 來避免。

最後,這裡有一些如何使用 shopt huponexit 的範例。3

$ shopt -s huponexit; shopt | grep huponexit
huponexit       on
# Background jobs will be terminated with SIGHUP when shell exits

$ shopt -u huponexit; shopt | grep huponexit
huponexit       off
# Background jobs will NOT be terminated with SIGHUP when shell exits

答案3

也許值得 -n在開始時嘗試選項ssh?它將防止遠端進程對本地進程的依賴stdin,當然本地進程一旦ssh session結束就會關閉。每當它嘗試訪問其stdin.

答案4

這聽起來更像是python腳本或其python本身正在做什麼的問題。真正要做的(除了簡化重定向)只是在運行程式之前將訊號nohup的處理程序設定HUP為(忽略)。一旦程式開始運行SIG_IGN,就沒有什麼可以阻止程式將其設定回SIG_DFL或安裝自己的處理程序。

您可能想要嘗試的一件事是將命令括在括號中,以便獲得雙分叉效果,並且您的python腳本不再是 shell 進程的子進程。例如:

( nohup python3 -u <script> & )

另一件可能也值得嘗試的事情(如果您正在使用bash而不是另一個 shell)是使用disown內建而不是nohup.如果一切都按記錄工作,這實際上不會產生任何影響,但在互動式 shell 中,這會阻止HUP訊號傳播到python腳本。您可以在下一行或與下面相同的行添加 disown(注意在 a;後面添加 a&是錯誤的bash):

python3 -u <script> </dev/null &>/dev/null & disown

如果上述或某些組合不起作用,那麼解決該問題的唯一位置肯定是python腳本本身。

相關內容