我有一个 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
仅当设置了 huponexit 选项时,当 ssh 连接关闭时,通过 ssh 连接打开的 shell 的所有子进程(无论是否处于后台)都会通过 SIGHUP 终止:运行 shopt huponexit 以查看是否属实。
如果 huponexit 为 true,则可以使用 nohup 或 disown 将进程与 shell 分离,这样退出时它就不会被杀死。或者,用屏幕运行东西。
如果 huponexit 为 false(目前至少在某些 Linux 上这是默认值),那么后台作业将不会在正常注销时被终止。
- 但即使 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
脚本本身。