ARTICLE DETAIL

资讯详情

深耕商务建站与企业官网运营的一线实战洞察。

Linux后台运行方法详解:nohup、setsid、screen与tmux实战

Linux后台运行方法详解:nohup、setsid、screen与tmux实战 搞Linux的人迟早都会碰上这么一档子事在远程终端上跑了个耗时任务跑一半网线被踢了、笔记本合盖休眠了、远程桌面关了再连上去发现程序已经没了之前跑了几个小时的进度直接清零。要是那个程序是个训练脚本、数据同步任务或者长时间编译损失就更让人崩溃了。这其实是Linux后台运行这个基本功没吃透。把nohup、setsid、screen、tmux这些方法捋明白之后你会发现“让程序在后台跑、断开连接也不影响”这件事根源就是一个叫SIGHUP的信号理解了它很多操作就都串起来了。这篇文章我把Linux里让程序后台运行的四种常用方法挨个讲透从最基础的到最稳妥的tmux每种的原理、适用场景、坑在哪都会结合我实际跑任务的例子说清楚。刚入门的学生、经常在服务器上跑实验的算法工程师还有要维护脚本和服务的运维都能在这篇里找到适合自己场景的方案。1. 先搞清楚你关掉终端时程序为什么会死1.1 控制终端、会话和进程组的三层关系在Linux里每当你在终端敲命令shell会为这条命令创建一个进程并且把它放进一个“进程组”。你操作的这个终端叫做这个会话的“控制终端”。正常情况下我们输入命令、程序打印日志都是通过这个控制终端完成的。这里最关键的一点是进程组、会话和控制终端是绑在一起的。当连接正常时它们一起工作一旦终端断开——比如ssh会话关闭、远程桌面断开——内核就会向这个会话的“首进程”发送一个 SIGHUP 信号挂断信号。首进程收到后默认动作就是终止自己而同一个进程组里的其他进程也会受到牵连。这个机制本身是Unix设计时为了规范用户登出行为定的。你想想如果一个用户通过终端登录到机器上后台挂了一堆程序用户直接关终端走人那些程序还留在系统里占着资源谁也控制不了它们系统很快就乱套了。所以内核干脆用一个SIGHUP通知整个会话“你的终端没了”大家一起收摊。1.2 后台运行的破局思路明白了SIGHUP这个机制后台运行的方法就可以归类了。让程序脱离“终端断开”的魔爪本质上只有三种思路让进程忽略SIGHUP信号这样终端断开时它不会被信号弄死nohup干的就是这事。让进程彻底脱离这个会话、脱离控制终端让它变成一个跟终端没有关系的独立进程setsid走的是这条路。给程序一个“虚拟终端”你人走了程序还在那个虚拟终端里跑着之后想回去也能接上这正是screen和tmux做的事。在后面的方法里你会反复看到这三种思路的影子。理解原理之后再去看那些命令就不会觉得它们是靠死记硬背的魔咒了。2. 四种后台运行方法逐个说透2.1 直接在命令后面加 最简单的临时后台python my_task.py 在命令末尾加一个shell就不用再等这个命令执行完了会立刻返回提示符让你能继续敲别的命令。命令对应的进程会进入后台运行标准输出还是会往当前终端打。这种方式的优点是零成本任何时候都能用。但如果你以为加了个就能关掉终端放心走那就踩了大坑。因为即使是后台任务的进程它依然在这个终端的会话和进程组里面关掉终端照样会收到SIGHUP然后挂掉。举个实际例子我有一个跑数据清洗的脚本大概半小时跑完我当时图省事直接python clean.py 然后合上笔记本就去吃饭了。回来一看命令在断开那会儿就没了清洗到一半的数据文件还是坏的。这之后就长教训了只适合配合nohup、setsid或者会话复用工具体使用单独用它来保长时间任务非常不可靠。跟相关的还有一组命令jobs看当前终端的后台任务列表fg把后台任务挪回前台bg把一个挂起的任务放到后台继续跑。比如你跑了个程序按了CtrlZ把它挂起再输入bg就能让它到后台运行。这组操作在同一个终端会话里临时切换前后台时很好用但同样解决不了“关终端就挂”的问题。2.2 nohup 加 最常用的“保命”组合nohup python my_task.py run.log 21 把命令丢到后台同时让进程忽略SIGHUP信号这就是nohup干的事。它会让程序在断线、关终端之后继续跑同时把标准输出和错误输出都重定向到文件里。这里加 run.log 21是标准操作不然nohup默认会把输出写到nohup.out这个文件里文件会越滚越大而且不好归类。我平时在远程服务器上跑Python脚本最常用的就是它比如跑一个要两个小时的爬虫nohup python crawler.py crawler.log 21 echo $! crawler.pid这里$!是shell里上一个后台进程的PID把它记到文件里后面要停任务的时候直接kill $(cat crawler.pid)就行了。很多笔记里只教你写nohup xxx 但记录PID这个小习惯能省不少事尤其在一个终端里启动过多个后台任务的时候靠ps去翻到底哪个是哪个真的很烦。判断一个nohup进程是否在正常跑可以随时用tail -f run.log看看日志有没有在更新。不过要注意nohup能让进程存活但它没法让你重新“接上”这个进程也就是说如果脚本跑到一半要你输入密码或者交互确认你就没辙了只能在启动前保证它不需要交互。提示nohup和配合不是必须二选一。光用nohup不加命令还是会占着前台光用不配nohup关终端就会挂。两个一起上才是稳妥的组合。2.3 setsid让进程开一个“新户口”setsid python my_task.py task.log 21 setsid的思路比nohup更彻底。它不是让进程“假装听不到”挂断信号而是直接让进程开启一个新的会话完全脱离原来的控制终端。用setsid启动的进程会成为新会话的首进程它的控制终端被彻底剥离自然也就不受原终端关闭的影响了。你可以用一条命令去看它的效果。先启动一个测试进程setsid sleep 300 然后用ps -ef看一下正常情况下它的PPID会变成1意思就是它被init/systemd进程收养了不再是某个终端下的小弟。这种“换了户口”的进程在系统里更像一个独立的服务。我一般在写批量脚本、希望某个子任务彻底跟当前环境撇清关系的时候用它。比如一个数据管道脚本里调用了另一个长时间运行的子程序想让子程序独立成会话不被父脚本的异常退出带着一起死就适合用setsid包一层。不过setsid有个要求启动时必须做好标准输入、标准输出和标准错误的重定向。因为它脱离了终端如果没重定向某些程序可能会因为找不到输出设备而出错。2.4 screen 和 tmux能“挂起”也能“回来”的方案如果说nohup和setsid是让程序“断舍离”那tmux以及老牌的screen就是给程序安排一个“可随时回去的虚拟房间”。tmux new -s train # 进入tmux会话后正常运行你的命令 python train.py --epochs 100 # 按 Ctrlb 再按 d脱离这个会话执行完Ctrlb d之后tmux会话里的程序还会继续跑你的终端会回到普通shell。这时候你关掉远程桌面、断开ssh都影响不了它。下次重新登录机器想看看训练进度tmux attach -t train就又能回到那个房间了屏幕上的输出还在随时可以继续操作。这一点是nohup完全没法比的。你不需要提前重定向日志因为随时都能回去看输出。screen是tmux的前辈功能类似screen -S train # 创建会话 screen -r train # 重新接入 # 脱离是 Ctrla 再按 d如果你只需要简单的后台交互会话screen就够了如果喜欢分屏、窗口管理更灵活用tmux更舒服。我个人现在是tmux重度用户平常登录服务器第一件事就是tmux a || tmux new所有长时间任务都在里面跑。把最常用的tmux快捷键列一下操作快捷键/命令用途新建会话tmux new -s 名称创建命名会话脱离会话Ctrlb然后d程序继续跑回到普通终端重新接入tmux attach -t 名称回来看进度、继续操作查看会话列表tmux ls列出所有会话关闭会话tmux kill-session -t 名称结束该会话里的所有程序水平分屏Ctrlb然后上下分两个窗格垂直分屏Ctrlb然后%左右分两个窗格提示如果你只是临时跑个长任务用nohup就行如果任务需要持续跟踪、可能要中途调整参数、或者你习惯分屏同时干几件事推荐直接上tmux。3. 四种方法怎么选我的实践推荐3.1 任务类型和方法匹配表这里把四种方法放在一起对比一下方便你结合场景做判断方法命令示例断线后是否存活能否重新接入交互适用场景python task.py 否否临时切换秒级任务nohup nohup python task.py log 21 是否一次性后台脚本只需看日志setsidsetsid python task.py log 21 是否脚本里启动独立子任务彻底脱离会话tmux / screentmux new -s task是是需要观察进度、可能中途介入的任务在服务器上长期跑Python训练任务时我最常用的组合是 tmux 起一个会话在会话里直接跑程序日志直接在终端滚动想看进度就attach回去不想看就脱出来干别的。如果任务本身是纯后台无人值守型不需要任何交互就用nohup。比如crontab定时任务、凌晨跑的告警脚本这种用nohup挂在后台日志写到文件里就够了。setsid的适用场景少一些但它有个特性很好用它可以让一个脚本在启动子程序时隔绝信号传播。比如你写了一个自动化编排脚本里面要依次启动好几个长时间运行的程序你不想父进程一挂就把子进程都带崩用setsid包住子程序就能保证每个程序自己的命运。3.2 使用后台任务时的几个细节不管用哪种方法有几个操作习惯我特别建议养成。把输出重定向写全。很多人nohup python xxx.py 一敲就完事结果程序输出的内容全部落到默认的nohup.out。短时间看不出来跑一整天之后这个文件可能膨胀到几个G把磁盘塞满。好习惯是每次启动都显式指定日志文件错误输出也一并合并进去。记录好PID。启动长时间任务后立刻把PID保存下来nohup python process_data.py process.log 21 echo $! process.pid后面想优雅停止就kill $(cat process.pid)想确认存活也可以kill -0 $(cat process.pid)。这个习惯在管理多个后台任务时价值巨大。凡是要交互、要输密码的程序别硬丢后台。有次我图省事直接nohup ansible-playbook xxx.yml 结果它跑到一半停下来等SSH密码输入日志再也没动静我又不在终端前只能kill掉重跑。后来遇到这类任务我都用tmux挂起再脱出需要输入的时候就重新接回去。3.3 如果你要长期跑不如直接上 systemd如果某个程序你需要它“7乘24小时一直跑开机自启挂了自动拉起”前面四种方法都不是最终归宿。nohup或tmux需要人工介入启动进程崩了不会自动拉起。这种场景更合适的方案是写成systemd服务。一个最简单的服务单元比如/etc/systemd/system/my-worker.service[Unit] DescriptionMy Data Worker Service Afternetwork.target [Service] ExecStart/usr/bin/python3 /opt/my_worker.py Restarton-failure RestartSec10 Userroot [Install] WantedBymulti-user.target配置好之后执行systemctl daemon-reload再systemctl enable --now my-worker就能开机自启、异常退出10秒后自动重启。这种方式比nohup和tmux正规得多适合做“服务”级别的任务。4. 后台任务常见问题排查实录4.1 明明加了 nohupssh断开后进程还是没了这是很多人最开始困惑的问题。排查思路先看启动命令里有没有把标准输入、输出、错误都处理干净。有些程序会打开终端设备文件或者依赖标准输入比如bash脚本里悄悄读了一次终端输入nohup管不到这种情况。还有一个容易被忽略的nohup放后台时如果那个进程后来又fork出了子进程某些特殊环境下子进程可能还是会挂在原会话下面。这种情况可以用setsid彻底换新会话解决。另外如果你用nohup bash script.sh 但脚本里又有exec或者重定向了文件描述符也可能导致信号处理链断掉排查起来绕一些但顺着“进程是否还在旧会话里”这个方向查原因一般就浮出来了。4.2 后台进程在但我不知道它卡在哪一步这就是我喜欢tmux的原因。如果是nohup任务日志文件里能看但有时候程序不写日志或者写到缓存里没刷盘看起来就像卡死了。这种时候可以用strace跟踪进程的系统调用看看它到底在等什么strace -p pid -e tracenetwork,file,write -f -tt它会输出进程当前正在处理哪些网络请求、正在读哪些文件。一般能很快定位到是在等网络响应、等文件锁还是等某个IPC资源。排查完记得让strace退出别一直挂着。4.3 程序启动了但文件被占用清理不了这是后台任务绕不开的问题。一个进程把某个文件或端口占住了你想删除、释放资源却被提示“Text file busy”或者端口占用。要在Linux下找到谁占用了两个命令最常用lsof 文件名 fuser -v 文件名 # 或者根据端口找进程 lsof -i :8080确认是哪个进程后判断它是不是还要继续跑。如果不该跑直接kill掉如果该跑那就别动它这和后台运行本身没关系属于资源管理的常规排查。4.4 日志中文全部变成乱码后台跑Python脚本print的中文日志经常会出现UnicodeEncodeError或者乱码。原因是很多终端环境默认编码是ascii而进程丢到后台后环境变量没继承全。我一般启动时显式指定PYTHONIOENCODINGutf-8 nohup python my_task.py run.log 21 这样Python的标准输出就主动用utf-8编码了。脚本里更稳的办法是开头加一行sys.stdout.reconfigure(encodingutf-8)完全绕开环境变量问题。4.5 进程成了孤儿进程或者僵尸进程用ps -ef看进程列表时有时能看到PPID为1的进程这种是孤儿进程被init/systemd接管了。如果这个进程本来就是你启动的“后台任务”且仍在正常运行那属于正常现象尤其用setsid启动的进程本来就会这样。如果看到状态列是defunct或者Z那是僵尸进程。僵尸进程是子进程死了但父进程没有调用wait回收它留下的“尸体”。后台运行的长时间脚本如果反复启动子进程又没做好回收就有可能出现一堆僵尸进程。这种问题的解法是让父进程正确回收子进程或者给父进程加上signal.SIGCHLD的处理逻辑实在不行直接kill掉父进程让systemd去收养和清理。4.6 tmux会话莫名其妙没了tmux会话一般很稳定但如果服务器重启所有tmux会话都会消失里面跑的程序也全没了。所以重要任务别只依赖tmux尽量同时配合日志落盘。另外如果你在tmux里跑了任务又想临时让任务不受tmux窗口关闭影响最好还是里面再用nohup套一层双保险。权限也会造成tmux会话“看起来丢了”。如果服务器上不同的用户各自创建了tmux会话用tmux ls默认只能看自己的。确认当前机器上所有会话tmux ls -S /tmp/tmux-$(id -u)/default权限不足时会话文件在别的用户目录下你自然看不到了。5. 我的后台运行工作流供你参考在实际工作中我一般把后台任务分成两档来管理。日常开发、实验调试、需要反复看输出和改参数的任务一律tmux。开一个tmux new -s dev里面分几个窗格一个窗格跑主程序一个窗格挂着tail日志另一个窗格留着敲命令。想离开环境了直接Ctrlb d脱离去干别的事过会儿tmux attach -t dev回来接着看。这套流程在远程桌面环境下特别顺手桌面断没断都不影响“会话还在”这件事。批量的、无人值守的任务比如数据同步、定时清洗、凌晨任务走nohup。启动时把日志写到一个固定目录文件名带任务名和日期同时把PID记录到文件。第二天来看日志确认任务跑完再把日志归档。这种做法简单直接而且日志和PID都在一个地方运维排查也方便。最后分享一个应急小技巧。有时候已经用CtrlZ把任务挂在后台了才发现终端要关来不及重新启动可以用disown -h %1这个命令会让当前shell不再向这个后台任务发送SIGHUP相当于临时给它套了个nohup的壳。终端断开后进程还能继续跑。不过要注意disown只是从当前shell的任务表里移除它并忽略挂断信号它本身并没有真正脱离会话复杂场景下不如直接用nohup或tmux稳。我自己的习惯是能不裸奔就不裸奔重要的任务至少叠加一层日志记录。毕竟再稳的方法也比不上出了问题后能快速定位原因记住这一点后台运行这件事就成功了一大半。
返回列表
PREV
查看更多资讯
NEXT
返回资讯列表