1. 写在前面
在 Linux 系统中,执行一些运行时间比较长的任务时,必须等待执行完毕才能断开 ssh 连接或关闭客户端软件,否则可能会导致执行中断。本文将介绍几种实用的技巧和工具(nohup、setsid、()、disown等),确保退出 ssh 登录后任务持续运行。
2. 为什么 ssh 注销后任务终止?
原因分析:
当用户注销(logout)或者网络断开时,终端会收到 HUP(hangup)/ SIGHUP 信号从而关闭其所有子进程。
jpzhang-dev:~$ ping www.bing.com > /dev/null &
[1] 1631074
jpzhang-dev:~$ pstree -H 1631074
systemd─┬─ModemManager───2*[{ModemManager}]
├─NetworkManager───2*[{NetworkManager}]
├─accounts-daemon───2*[{accounts-daemon}]
├─snapd───23*[{snapd}]
├─sshd───sshd───sshd───bash─┬─ping
│ └─pstree
... ...
从上面的例子:
... ...
├─sshd───sshd───sshd───bash─┬─ping
│ └─pstree
... ...
可以看出当前所处的 bash 是 sshd 的子进程,当 ssh 断开连接时,HUP 信号会影响到它下面的所有子进程,包括示例中的 ping 进程,指示它们终止,这是为了释放系统资源并确保没有孤立进程在运行。
HUP (hangup) 信号:
在 Linux 和 Unix 系统中,HUP(Hangup)信号是一个重要的信号,通常被表示为 SIGHUP。通常在用户注销、终端关闭或网络连接断开时发送。
SIGHUP 的主要作用:
通知进程终止:当终端连接中断或用户注销时,系统会向与该终端相关联的所有进程发送 SIGHUP 信号,默认情况下,这些进程会收到该信号并终止。
重启进程:许多守护进程(daemon)会捕获 SIGHUP 信号,以便在收到信号时重新加载配置文件,而不是完全停止进程。例如,nginx、httpd 等服务会在接收到 SIGHUP 信号后重读其配置。
实现进程管理:系统管理员可以使用 SIGHUP 信号来控制服务的行为,例如通过命令 kill -HUP
向特定进程发送 SIGHUP,使其重新加载配置或优雅地结束。
进程可以通过设置信号处理程序来捕获 SIGHUP 信号。以下是一个简单的示例(使用 Python):
py_signal.py:
import os
import signal
import time
def handle_sighup(signum, frame):
print("Received SIGHUP signal!")
# 注册信号处理器
signal.signal(signal.SIGHUP, handle_sighup)
# 模拟进程持续运行
print("Process is running. PID:", os.getpid())
while True:
time.sleep(1)
终端1: 运行脚本
jpzhang-dev:~/workspace/linux$ python3 py_signal.py
Process is running. PID: 1633852
Received SIGHUP signal!
终端2: 发送 SIGHUP
kill -HUP 1633852
终端1,输出 "Received SIGHUP signal!"
查询更多信号:kill -l、man signal、man 7 signal
解决思路:
让进程运行在新的 session(会话)里,即不属于此终端的子进程;
让进程忽略 HUP 信号;
3. 技巧 & 工具
【注意】下文提到的快捷键,不区分字母大小写;
3.1 nohup 命令
nohup(no hang up)命令用于在 Linux/Unix 系统中运行进程,使其在用户注销或终端关闭后继续运行。默认情况下,nohup 会忽略 SIGHUP 信号,从而确保进程不会被终止。
语法:
nohup command [arguments] &
command:要执行的命令或程序;[arguments]:传递给命令的参数(可选);&:将命令放入后台执行,允许你继续使用终端;
示例:
启动一个长时间运行的命令,比如 ping,并使用 nohup:
nohup ping www.bing.com &关闭此终端,打开另一个终端使用 ps 命令,查看到后台有 ping 进程在运行,终端输出(stdout、stderr)将附加到当前目录的 nohup.out 文件中。如果当前目录的 nohup.out 文件不可写,输出将重定向到 $HOME/nohup.out 文件中。
jpzhang-dev:~$ ps -ef | grep ping
root 1643510 1638994 0 11:06 pts/4 00:00:00 ping www.bing.comjpzhang-dev:~$ tail -f nohup.out
64 字节,来自 202.89.233.100 (202.89.233.100): icmp_seq=53 ttl=116 时间=23.9 毫秒
64 字节,来自 202.89.233.100 (202.89.233.100): icmp_seq=54 ttl=116 时间=23.2 毫秒
... ...重定向输出:
nohup ping www.bing.com > output.log 2>&1 &> output.log: 将标准输出重定向到 output.log 文件;2>&1: 将标准错误重定向到标准输出,使所有输出都写入同一个文件;
3.2 setsid 命令
setsid 命令用于在 Linux/Unix 系统中创建一个新的会话,并将指定的程序在新的会话中运行,脱离当前终端,使其不再受到终端的控制和影响。
语法:
setsid command [arguments]
command:要执行的命令或程序;[arguments]:传递给命令的参数(可选);
示例:
setsid ping www.bing.com > /dev/null
或
setsid ping www.bing.com > output.log 2>&1
jpzhang-dev:/home/workspace/linux# ps -ef | grep ping
root 1645405 1 0 11:21 ? 00:00:00 ping www.bing.com
从上例可以看出 ping 进程的 PID 是 1645405,进程的父 ID(PPID) 是 init(1) 而不是当前终端的进程 ID,可与 nohup 比较。
3.3 将 "&" 放入 “()” 内执行命令
在 Linux/Unix 系统中,将命令放在 () 中并使用 & 可以创建一个子进程,并在后台执行该命令。这种方法可以在新的子 Shell 中运行多个命令,而不会影响当前的 Shell 环境。
语法:
( command1; command2; ... ) &
command1; command2; ...:可以是多个命令,使用分号 ";" 分隔;():表示在子 Shell 中执行命令;&:将整个命令组放入后台执行;
示例:
(ping www.bing.com > /dev/null &)
或
(ping www.bing.com > output.log 2>&1 &)
jpzhang-dev:/home/workspace/linux# (ping www.bing.com > /dev/null &)
jpzhang-dev:/home/workspace/linux# ps -ef | grep ping
root 1645585 1 0 11:22 pts/4 00:00:00 ping www.bing.com
进程的父 ID 是 init(1) 而不是当前终端的进程 ID,因此关闭当前终端无任何影响。
3.4 disown 命令
disown 命令的功能是用于从 Shell 终端中移除作业,是一个 bash 解释器的内置命令。disown 命令能够从 Shell 终端中移除指定的作业,正在运行的作业也可以被移除,亦可以对作业进行标记,使其在当前 Shell 终端窗口关闭后依然不会结束。
【重点】disown 命令可以移除当前正在运行的作业(后台作业)或者通过 “Ctrl + Z”,确保它在终端关闭或 ssh 会话断开后继续运行;
语法:
disown [options] [job_spec]
options:
-h:标记作业使其结束 Shell 终端窗口时不退出;-a:移除所有的作业;-r:移除运行的作业;-ah:所有的作业都忽略 HUP 信号;-rh:正在运行的作业忽略 HUP 信号;job_spec:作业编号(例如 %1、%2)或作业名称。可以使用 jobs 命令查看后台作业编号。如果不指定 job_spec,disown 会移除当前 Shell 的最后一个作业。
当使用过 disown 命令后,会将把目标作业从作业列表中移除,不能再使用 jobs 来查看它,但是依然能够用 ps -ef 查找到它。
如下示例所示:
jpzhang-dev:~# ping www.bing.com > /dev/null &
[1] 1665969
jpzhang-dev:~# jobs
[1]+ 运行中 ping www.bing.com > /dev/null &
jpzhang-dev:~# disown %1
jpzhang-dev:~# jobs
jpzhang-dev:~# ps -ef | grep ping
root 1665969 1642922 0 14:00 pts/5 00:00:00 ping www.bing.com
关闭终端,再次打开查询进程,可以看到进程的父 ID(PPID) 是 init(1):
jpzhang-dev:~# ps -ef | grep ping
root 1665969 1 0 14:00 ? 00:00:00 ping www.bing.com
但是还有一个问题,这种方法的操作对象是作业,如果我们在运行命令时在结尾加了 "&" 来使它成为一个作业并在后台运行,那就没问题,可以通过 jobs 命令来得到所有作业的列表。
但是如果并没有把当前命令作为作业来运行,如何才能得到它的作业号呢?答案就是用 "Ctrl + Z"(按住 Ctrl 键的同时按住 Z 键)。
如下示例所示:
# Ctrl + Z 将程序放入后台,暂停执行(挂起进程)
jpzhang-dev:~# ping www.bing.com > /dev/null
^Z
[1]+ 已停止 ping www.bing.com > /dev/null
jpzhang-dev:~# jobs
[1]+ 已停止 ping www.bing.com > /dev/null
# 继续执行后台的作业
jpzhang-dev:~# bg %1
[1]+ ping www.bing.com > /dev/null &
jpzhang-dev:~# jobs
[1]+ 运行中 ping www.bing.com > /dev/null &
# 此时 jobs 还是能看到 ping 后台任务的。
jpzhang-dev:~# ps -ef | grep ping
root 1667480 1665681 0 14:12 pts/7 00:00:00 ping www.bing.com
# 标记作业使其结束 Shell 终端窗口时不退出
jpzhang-dev:~# disown -h %1
# 需要断开 ssh 链接后,重新登录
jpzhang-dev:~# ps -ef | grep ping
root 1667480 1 0 14:12 ? 00:00:00 ping www.bing.com
可以看到进程的父 ID(PPID) 是 init(1)。
Ctrl + Z 的用途就是将当前进程挂起(Suspend),然后我们就可以用 jobs 命令来查询它的作业号,再用 bg jobspec 来将它放入后台并继续运行。需要注意的是,如果挂起会影响当前进程的运行结果,请慎用此方法。
命令整理:
$ sleep 500
# 假设有一个程序在前台运行,请按 Ctrl + Z(挂起):
^Z
[1]+ Stopped myprogram
# 标记作业使其结束 Shell 终端窗口时不退出,如果只有一个作业,则无需指定作业编号:disown -h
$ disown -h %1
# 继续执行后台的作业,如果只有一个作业,则无需指定作业编号:bg;("fg %1" 重新回到前台运行)
$ bg %1
[1]+ sleep 500 &
$ logout
$ ps -ef | grep sleep
root 1668456 1 0 14:18 pts/4 00:00:00 sleep 500
3.5 screen 命令
screen 是 Linux/Unix 系统中的一个多路复用终端工具,允许用户在一个终端会话中运行多个程序,尤其是在 ssh 连接断开或关闭终端时,它能够保持会话和进程的运行状态。
功能:
保持会话活跃:即使 ssh 连接断开或终端关闭,正在运行的任务依旧继续;
恢复会话:断开后可以重新连接到同一个会话,查看任务执行情况;
多任务管理:在一个 screen 会话中可以运行多个终端任务,类似于多标签的终端操作;
会话分离与恢复:可以随时将会话分离(detach),稍后再恢复(reattach);
安装:
# CentOS
$ yum install screen
# Debian/Ubuntu
$ apt install screen
语法:
screen [options] [command]
screen:启动一个新的 screen 会话;screen -S session_name:创建一个带有自定义名称的会话;screen -r [session_name]:恢复一个分离的会话;screen -ls:列出当前存在的所有 screen 会话;Ctrl + A + D:分离当前会话,保持程序运行但退出到普通终端;
示例:
运行长时间任务并确保不中断
# 进入 screen 后运行 ping 任务
screen -S ping_session
或
# 直接在启动 screen 时指定要运行的命令
screen -S ping_session ping www.bing.com > /dev/nullCtrl + A + D:分离当前会话,保持程序运行但退出到普通终端。列出所有 screen 会话:
jpzhang-dev:~# screen -ls
There are screens on:
1673330.ping_session (10/10/24 14:56:40) (Detached)
3424088.pts-4.jpzhang-dev (09/27/24 09:52:59) (Detached)
2 Sockets in /run/screen/S-root.恢复分离的 screen 会话:
# 如果只有一个分离(Detached)的 screen 会话,可以直接使用 screen -r 来恢复
screen -r 1673330
或
screen -r ping_session关闭 screen 会话:
在 screen 会话中执行 exit,会话会自动关闭;
若没有使用分离,直接关闭终端连接时,screen 会话和进程也会结束;
多任务并行管理
启动 screen 会话后,你可以通过
Ctrl + A + C创建多个窗口,在不同窗口中运行多个任务。例如,窗口 1 :代码编译、窗口 2:监控日志,窗口 3:网络测试。screen -S multitask新建窗口:在 screen 会话内按
Ctrl + A + C创建新窗口,并在每个窗口中执行不同的任务;切换窗口:
Ctrl + A + N(下一个窗口)或Ctrl + A + P(上一个窗口);分离会话:
Ctrl + A + D返回到普通终端;
3.6 tmux 命令
tmux 是一款强大的终端多路复用器(terminal multiplexer),类似于 screen,它允许在单个终端窗口中运行多个会话、分屏显示、共享会话等。
功能:
会话持久性:ssh 断开后,tmux 会话及其运行中的任务依然保留,可以重新连接;
会话管理:可以创建多个会话,每个会话可以包含多个窗口和子任务;
窗口和分屏:一个 tmux 会话中可以打开多个窗口,每个窗口可以再分成多个分屏(pane),让你同时运行和监控多个任务;
会话共享:你可以和其他用户共享 tmux 会话,进行协作;
安装:
# Ubuntu 或 Debian
$ apt-get install tmux
# CentOS 或 Fedora
$ yum install tmux
语法:
tmux [options] [command]
tmux:启动一个新的 tmux 会话;tmux new -s session_name:创建一个带有自定义名称的会话;tmux attach -t session_name:重新连接到一个已有的会话;tmux ls:列出所有当前存在的 tmux 会话;tmux detach:分离当前会话(保持任务运行);
示例:
使用 tmux 保持 ssh 会话
tmux new -s long_task
# 执行任务: ping www.bing.com
Ctrl + B, D # 分离会话: 先按 Ctrl + B, 在键入 D(注:字母键,不区分大小写)
# 断开 SSH,通过以下命令恢复
tmux attach -t long_task
# 关闭 tmux 会话
# 在 tmux 会话内执行 exit 命令创建多个窗口和分屏
tmux new -s dev
# 创建新窗口
Ctrl + B, C
# 在当前窗口垂直分屏
Ctrl + B, %
# 在当前窗口水平分屏
Ctrl + B, "
# 切换窗口
Ctrl + B, N/P # 上下窗口
Ctrl + B, 0-9 # 切换到指定编号的窗口
# 切换分屏
Ctrl + B, O
Ctrl + B, [方向键]
# 关闭分屏,分屏内执行 exit 命令
tmux、screen 功能对比:
| 功能 | tmux | screen |
|---|---|---|
| 基本功能 | 提供终端多路复用、分屏、会话保持等功能 | 提供终端多路复用、分屏、会话保持等功能 |
| 会话管理 | 支持命名会话,便于管理和恢复 | 也支持命名会话,但语法和管理方式不如 tmux 灵活 |
| 分屏管理 | 支持窗口内的水平和垂直分屏,操作简单 | 支持窗口分屏,但控制较少,分屏操作不如 tmux 灵活 |
| 窗口管理 | 直接支持多窗口、多个子任务 | 也支持多窗口管理,但操作上相对复杂 |
| 快捷键 | 默认快捷键组合以 Ctrl + B 开头,可自定义。字母键,不区分大小写 | 默认快捷键组合以 Ctrl + A 开头,可自定义。字母键,不区分大小写 |
| 滚动缓冲 | 更易用的滚动缓冲区,支持快捷键或鼠标滚动 | 支持滚动缓冲区,默认情况下需要进入复制模式 |
| 会话共享 | 支持多个用户同时连接并操作同一会话 | 也支持,但操作复杂 |
| 配置文件 | 配置文件更简洁灵活,支持脚本化的自动化配置 | 配置文件较复杂,功能较为基础 |
4. 总结
现在几种方法已经介绍完毕,可以根据不同的场景来选择不同的方案:
&: 将进程置于后台,使 Shell 不用等待它的结束而继续接受用户输入 (stdin);nohup/setsid/(): 无疑是临时需要时最方便的方法;disown: 能帮助我们事后补救当前已经在运行了的作业,若需要 Ctrl + Z 挂起进程,需考虑是否影响当前进程的运行结果;tmux、screen: 则是在大批量操作时不二的选择了;




