一 场景引入
我们经常会碰到这样的问题,用 telnet/ssh 登录了远程的 Linux 服务器,运行了一些耗时较长的任务, 结果却由于网络的不稳定导致任务中途失败。如何让命令提交后不受本地关闭终端窗口/网络断开连接的干扰呢?
当用户注销(logout)或者网络断开时,终端会收到 HUP(hangup)信号从而关闭其所有子进程。
因此,我们的解决办法就有两种途径
1)要么让进程'忽略 HUP 信号' --> 'nohup'
2)要么让进程运行在'新的会话里'从而成为"不属于此终端的子进程" --> 'setsid'
二 案例
可以'针对不同的场景'选择'不同的方式'来处理这个问题
(1)nohup
(1) nohup命令 可以将'程序以忽略挂起信号'的方式运行'running'起来,被运行的程序的输出信息将'不会显示到终端'
(2) 无论是否将 nohup 命令的输出重定向到终端,输出都将'附加到'当前目录的 'nohup.out' 文件中
(3) 如果当前目录的 nohup.out 文件'不可写',输出'重定向到$HOME/nohup.out文件中'
(4) 如果'没有文件'能创建或打开以用于追加,那么 'command 参数'指定的命令'不可调用'
(5) 如果标准错误'是一个终端',那么把指定的命令写给'标准错误的所有输出'作为标准输出重定向到相同的文件描述符
案例
1: '脚本(或者command)方式后台运行' --> '不产生nohup.txt'
nohup ./shell.sh &> /dev/null &
2: '将错误输出也伪装成标准输入'
nohup command > myout.file 2>&1 &
注意细节
正确方式: 当shell中'提示了nohup成功后'还需要'按终端上键盘任意键'退回到shell输入命令窗口,然后通过在shell中'输入exit来退出终端'
错误方式: 而我是每次在nohup执行成功后'直接点关闭程序按钮关闭终端',所以这时候会'断掉该命令所对应的session',导致nohup对应的进程被通知需要一起shutdown。
(2)setsid
setsid命令子进程'从父进程继承了':SessionID、进程组ID和打开的终端,子进程如果'要脱离这些',代码中可通过'调用setsid来实现'
命令行或脚本中可以通过使用命令setsid来运行程序实现。setsid'帮助一个进程脱离从父进程继承而来的已打开的终端'、隶属进程组和隶属的会话。
--- '更加通俗的理解' ---
setsid中的sid指的是session id,意指以该命令运行的进程是一个'新的session',因此'其父进程id不属于当前终端',实际上,setsid运行的进程,其'父进程id(PPID)为1'(init 进程的 PID)
ps -ef |grep --> '可以查看ppid=1'
使用方法同上
注意
setsid输出重定向必须'手动指定'
二者的关系
由于nohup的父进程与'当前的worker有关',当我们Ctrl+C的时候'没有放到后端',也会'把其给kill掉'。
而setsid的'父进程是init',所以当我们'退出worker的时候',并'不会kill掉该服务'
(3)subshell
First: 将一个或多个命令包含在'()'中就能让这些命令在'子 shell 中'运行中
Second: 将"&"也放入'()'内之后,我们就会发现'所提交的作业并不在作业列表中',也就是说是'无法通过jobs来查看'的
(ping 172.25.2.100 &)
ps -ef |grep ping
补充说明
'jobs' 可以看到'该进程的父进程'是当前shell的进程号
(4)disown
场景引入:我们已经知道,如果事先在命令前加上 nohup 或者 setsid 就可以避免 HUP 信号的影响。但是如果我们未加任何处理就已经提交了命令,对于已经在后台运行的命令,该如何补救才能让它避免 HUP 信号的影响呢?
用法
disown -h %'jobspec' 来使某个作业 '忽略HUP信号'
disown -ah 来使'所有的'作业都忽略HUP信号
disown -rh 来使'正在运行'的作业 忽略HUP信号
(1) disown是'bash内部命令',nohup是外部命令
(2) disown跟bg,fg一样是'针对job'进行操作,nohup是针对'命令'操作
(3) 退出后'归属于init进程管理'
(4) 当使用过 disown 之后,会将把目标作业'从作业列表中移除',我们将不能再使用jobs 来查看它,但是依然能够用'ps -ef 查找到它'
备注: '最后一条似乎没有生效'
说明:只有'当前终端'退出后,才会隶属于'init'进程管理 --> 再'开启一个'终端查看
案例展示
disown (如果'提交命令时''未使用&将'命令放入后台运行,可使用' CTRL-z '和'bg'将其放入后台,再使用"disown")
[root@pvcent107 build] ' cp -r testLargeFile largeFile2'
[1]+ Stopped cp -i -r testLargeFile largeFile2
[root@pvcent107 build] ' bg %1'
[1]+ cp -i -r testLargeFile largeFile2 &
[root@pvcent107 build] jobs
[1]+ Running cp -i -r testLargeFile largeFile2 &
[root@pvcent107 build] 'disown -h %1'
[root@pvcent107 build] ps -ef |grep largeFile2
root 5790 5577 1 10:04 pts/3 00:00:00 cp -i -r testLargeFile largeFile2
root 5824 5577 0 10:05 pts/3 00:00:00 grep largeFile2
[root@pvcent107 build]#
(5)ctrl+z补充
'disown这种方法'的操作对象是'作业',如果我们在运行命令时在结尾加了"&" 来使它成为一个作业并在后台运行,那么就万事大吉了,我们可以通过jobs 命令来得到所有作业的列表。
但是如果起初并'没有把当前命令作为作业来运行',如何才能'得到它的作业号'呢?答案就是用 'CTRL-z(按住Ctrl键的同时按住z键)'了!
CTRL-z 的用途就是'将当前进程挂起'(Suspend),然后我们就可以'用jobs 命令来查询它的作业号',再用'bg jobspec 来将它放入后台'并继续运行。
需要'注意'的是:如果挂起会'影响当前进程的运行结果',请'慎用'此方法。
screen, tmux --> 将进程永远'处在后台运行'是
(6)ssh后台运行案例补充
需求: ssh远程执行脚本并在后台运行 --> 要不要加 'nohup'
ssh root@172.16.146.20 '/usr/local/luly/loadavg.sh 2 &> /dev/null &'