ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

关闭MobaXterm窗口后Linux程序如何后台运行

关闭MobaXterm窗口后Linux程序如何后台运行 半夜两点挂上一个数据处理脚本随手把 MobaXterm 的窗口叉掉第二天早上打开一看进程没了日志停在 3%——这种经历做过服务器运维或者跑过训练任务的人基本都遇到过至少一次。很多人第一反应是服务器是不是被重启了登录上去uptime一看机器好好地跑了几十天压根没重启问题就出在你在 MobaXterm 里敲下的那条命令是挂在那个终端窗口上的。窗口一关终端消失内核顺手把你的进程也带走了。这篇东西就是围绕关闭 MobaXterm 等连接 Linux 服务器的软件窗口程序依旧在后台跑这件事把背后的信号机制、几种主流方案的取舍、以及我在实际使用中踩过的坑一次性讲透。关键词里提到的 MobaXterm、Linux、服务器、后台运行程序全都是围绕这个核心问题展开的。无论你是刚学会ssh登录的新手还是已经管着几台云服务器的运维这里面的内容都能直接抄去用。特别是那些跑长时间任务——模型训练、批量转码、数据清洗、爬虫、压测——的人看完之后应该不会再出现一关窗口就前功尽弃的情况。1. 断开连接后进程为什么会死终端、会话与 SIGHUP 的三方关系要解决关窗口程序就死先得搞清楚它为什么会死。很多人以为是自己操作有问题其实是 Linux 进程模型的正常行为只是这个行为在图形化客户端里被隐藏得很好让人产生了错觉。1.1 一次典型的任务凭空消失现场我在早期做数据清洗的时候有过一次很典型的翻车。当时在服务器上跑一个用 Python 写的日志解析脚本处理大概 40GB 的文本预计要跑三四个小时。我本地是用 MobaXterm 连上去的脚本跑起来之后屏幕上刷刷地滚日志看着挺爽。结果笔记本没电自动关机了MobaXterm 的连接断开等我换台电脑重新登录ps -ef | grep python里已经找不到那个进程输出文件大小停在 1.2GB 不再增长。当时的疑惑点是服务器明明一直在运行为什么进程会跟着我的笔记本一起死答案在于进程并不是绑在服务器上的而是绑在那个 SSH 会话所对应的伪终端上的。你的笔记本只是连接的一端但真正确立归属关系的是服务器上那个pts/x伪终端设备。连接断开伪终端关闭内核就按照既定规则向挂在它上面的进程发信号默认行为就是终止。1.2 SIGHUP 才是真正的凶手Linux 里进程之间靠信号通信kill -l能列出一大堆。这里面和断开连接直接相关的是编号为 1 的SIGHUP名字来自早期的串口调制解调器时代——电话线挂断hang up时给进程发这个信号。到了今天它的触发场景变成了控制终端关闭时内核向该终端的前台进程组发送 SIGHUP。这条规则有几个关键点容易被忽略发给的是前台进程组不是单个进程。所以你在前台跑的命令以及它 fork 出来的子进程如果还在同一个进程组里会一起收到。进程对这个信号的默认动作是终止而且是那种不留遗言的终止不会有清理逻辑临时文件、锁、半写状态的数据都会原样留在磁盘上。如果一个进程忽略或者捕获了 SIGHUP那么它就不会死。这就是所有后台常驻方案的技术基础。顺带说一句bash自己也会收到 SIGHUP。收到之后它会把当前 shell 里的作业jobs再补一刀向它们转发 SIGHUP。所以有些情况下即使你用了把进程放到后台窗口一关照样死——因为进程只是后台运行并没有免疫 SIGHUP。1.3 前台进程组、后台进程组与作业控制在终端里敲sleep 100 输出的[1] 12345里的1是作业号12345是进程号。这个进程被放进了一个新的后台进程组但它仍然属于当前会话控制终端还是那个pts/x。这里的区别很微妙我一般用下面这张表来跟新人解释状态控制终端归属收到 SIGHUP 吗关窗口后前台运行cmd当前 pts是死后台运行cmd 当前 pts是默认死nohup cmd 脱离否被忽略活screen/tmux里的cmd虚拟终端否在另一会话里活systemd 管理的服务无终端否活且能自启这张表基本概括了所有场景。看明白这张表后面几节的内容你就能自己推导出七八成。1.4 怎么判断一个程序怕不怕断线不是所有程序都怕断线。有几种情况其实是可以放心关窗口的用systemctl、service启动的系统服务它们本来就不依赖终端关窗口毫无影响。用crontab定时起的任务跑的时候没有控制终端也不受影响。已经用daemon()或者双重 fork 把自己变成守护进程的程序比如 nginx、mysqld天生就不绑终端。反过来只要你是在 SSH 命令行里手工敲上去的交互式命令不管它是python、java -jar、ffmpeg还是rsync默认都是怕断线的。判断方法很简单跑起来之后执行ps -o pid,ppid,pgid,sid,tty,cmd -p PID看TTY这一列。如果是pts/0之类说明它还挂在某个伪终端上关掉那个终端它就有危险如果是?说明它已经没有控制终端了随便关窗口都不影响。2. nohup 加 到底做了什么一次把参数讲清楚nohup是解决这个问题最轻量的手段几乎每台 Linux 上都自带不需要额外装东西。但它的行为比大多数人想象的要朴素——它并不负责把进程变成守护进程只做了有限的几件事。2.1 nohup 实际只做了两件事nohup的源码逻辑非常短核心就两个动作把SIGHUP的处理方式设置为忽略SIG_IGN。这样终端关闭时内核发来的 SIGHUP 就被丢掉了。检查标准输出是不是终端如果是就把 stdout 重定向到当前目录的nohup.out如果当前目录不可写就落到$HOME/nohup.out。注意第二个动作的关键词——如果是终端。也就是说如果你自己已经用重定向过 stdoutnohup就不会再插手。这也解释了一个常见困惑为什么有时候会莫名其妙多出一个nohup.out文件有时候又不会。它没有做这些事没有 fork 出新会话、没有setsid、没有脱离控制终端、没有改变工作目录。所以严格来说nohup之后的进程仍然属于原来的会话只是它脸皮厚收到 SIGHUP 也不理而已。2.2 输出重定向必须自己接管 stdout 和 stderr这是最容易出问题的一环。很多人写nohup python app.py 就完事了运行一段时间后发现两件事一是目录里冒出一个巨大的nohup.out二是程序莫名其妙卡住不动。卡住的原因在于进程的 stdout/stderr 文件描述符仍然指向那个伪终端。终端关闭后这个 fd 变成写不出去的状态。当程序的输出量大到把缓冲区填满时写操作就会阻塞进程就卡住了。表现就是进程还在但死活不动CPU 占用为零。所以正确写法是把两个流都显式接管nohup python train.py /data/logs/train.log 21 拆解一下 /data/logs/train.log把 stdout 指向文件。21把 stderr 也指向 stdout 当前所在的位置也就是同一个文件。顺序不能反写21 file是错的那样 stderr 还是指向终端。结尾的让命令进入后台shell 立刻把提示符还给你。更稳妥的做法是连 stdin 也一起处理掉防止程序在某个地方等待输入nohup python train.py /dev/null /data/logs/train.log 21 我习惯再加一步把 PID 记下来方便后续管理mkdir -p /data/run nohup python train.py /dev/null /data/logs/train.log 21 echo $! /data/run/train.pid$!是 shell 的一个特殊变量表示最后一个进入后台的进程的 PID。有了这个 PID后面要停任务就kill $(cat /data/run/train.pid)不用再去ps里翻。2.3 已经跑起来了才想起要后台运行怎么办现实里更常见的情况是程序已经在前台跑着了日志刷得飞快你突然意识到待会儿可能要断线这时候不想重启它。有两个补救手段。第一个是暂停 转后台。用CtrlZ把前台进程挂起注意不是终止然后bg %1 disown -h %1bg让挂起的作业在后台继续跑disown -h把它从 shell 的作业表里摘掉并且标记为不接收 SIGHUP。这样 shell 自己收到 SIGHUP 转发作业的时候就会跳过它。不加-h的话是直接从作业表删除但那样你就不能用%1这类作业号了两者按需选。第二个是事后重新接管。如果进程的 stdout 还指着终端可以试试用reptyr把它搬到一个新的 tmux 会话里reptyr PID这个工具在大多数发行版的仓库里都有但需要内核ptrace权限配合不少系统默认限制了yama/ptrace_scope可能要临时调整。这东西属于应急手段能用但不保证百分百成功别把它当常规方案。2.4 setsid彻底脱离控制终端的另一种选择如果你不想要nohup.out这种副作用也不想被 shell 的作业控制牵连可以用setsidsetsid python train.py /dev/null /data/logs/train.log 21 setsid会调用setsid()系统调用让目标进程成为一个新会话的首进程同时脱离原有的控制终端。既然没有控制终端了自然也就不会收到终端关闭引发的 SIGHUP。它和nohup的区别是nohup是收到信号也不理setsid是根本收不到信号。两条路都走得通setsid更干净一点代价是有些老系统上的setsid行为略有差异比如不加-f时可能不会 fork导致 shell 的行为和预期不一致。我个人的习惯是临时性、跑几个小时的任务用nohup加显式重定向需要绝对干净、跑几天的用setsid长期存在的用下面的 systemd 方案。3. screen 与 tmux把会话留在服务器上而不是客户端上nohup解决了进程不死但没解决另一个需求我想看到程序的实时输出还想跟它交互。一个跑三个小时的交互式程序你总不能一直tail -f日志或者写个依赖input()的脚本然后发现它断了就卡住。这时候需要的是终端复用器。3.1 思路的差别脱离 vs 托管nohup的思路是让进程脱离终端而screen/tmux的思路是在服务器上造一个虚拟终端把你的程序放在里面。你从 MobaXterm 连过去只是看到了这个虚拟终端的画面断开连接虚拟终端和里面的程序都还在服务器内存里活着下次连上来再接回去看到的还是原来的画面光标停在原来的位置。这个差别很重要。它意味着用screen/tmux你不仅能保住进程还能保住交互现场——比如一个正在运行的top、一个半交互的 REPL、一个等着你按回车确认的脚本。3.2 screen 的最小可用命令集screen出现得早几乎所有发行版都有甚至有些精简系统只带 screen 不带 tmux。它的常用操作就下面这几条screen -S work # 新建一个叫 work 的会话 # 在里面正常跑你的程序 # CtrlA 然后按 D - detach回到普通 shell screen -ls # 列出所有会话 screen -r work # 重新接入 work 会话 screen -d -r work # 如果它显示 Attached先踢掉之前的连接再接入 screen -S work -X quit # 彻底杀掉这个会话里面的程序也会被结束有个细节要注意screen -r如果提示There is a screen on ... Attached说明上一次的连接状态没被正确清理比如网络断了但 TCP 没超时。这时候用-d -r组合先 detach 再 attach一般都能解决。如果还是不行screen -ls会给出 PID可以kill掉那个 screen 进程但那样里面的程序就没了慎用。还有一个新手常踩的坑CtrlA是 screen 的转义键在 screen 里想跳到行首是不能用CtrlA的得按两次。这个习惯冲突让不少人第一次用 screen 时很抓狂可以用-e参数换一个不常用的转义键或者干脆上 tmux。3.3 tmux 的会话、窗口、面板三层结构tmux 的模型比 screen 清晰分三层session会话最外层一个 session 就是一个独立的环境对应一次项目。window窗口session 里的标签页一个 session 可以有多个 window。pane面板window 里的分屏一个 window 可以切成多个 pane。基本操作tmux new -s work # 新建会话 # CtrlB 然后 D # detach tmux ls # 列出会话 tmux attach -t work # 接入 tmux kill-session -t work # 干掉整个会话窗口和面板的操作前缀都是CtrlB快捷键作用CtrlBC新建窗口CtrlBN/P下一个 / 上一个窗口CtrlB%左右分屏CtrlB上下分屏CtrlB方向键在面板间切换CtrlBDdetach 整个会话CtrlB[进入复制模式可上下翻页看历史输出最后那个复制模式非常实用。跑长任务的输出动不动几千行直接看屏幕只能看到最后几十行进复制模式就能翻回去q退出。我通常会把历史缓冲区调大写进~/.tmux.confset -g history-limit 50000 set -g mouse on setw -g mode-keys vimouse on之后可以用鼠标滚轮翻页、拖动调整面板大小体验会好很多。mode-keys vi让复制模式支持 vi 风格的移动键。改完配置文件执行tmux source-file ~/.tmux.conf生效或者下次启动自动加载。3.4 断线重连后的现场恢复用 tmux 的一个典型流程是这样的早上连上去tmux new -s etl在里面跑一个数据同步脚本看着它开始刷日志中午要出门按CtrlB D或者直接把 MobaXterm 关掉下午换个网络环境重新连上服务器tmux attach -t etl画面立刻回到上午断开时的样子脚本该跑到哪儿还跑到哪儿。这里有个容易忽略的点tmux 会话是跟着服务器走的不是跟着用户走的。如果服务器重启所有 tmux 会话都会消失。所以 tmux 保的是断线不死不保重启不死。要保重启得看下一节的 systemd。另外多个用户同时 attach 同一个 tmux 会话是可以的会看到同一块屏幕这在结对排查问题时挺好用但要注意两个人同时输入会互相干扰。3.5 三种方案怎么选我把这几节的方案整理成一张对照表方便直接决策方案断线不死能交互服务器重启后自动拉起适合场景nohup/setsid是否否跑几小时的批处理、脚本screen/tmux是是否需要看输出、需要交互、调试期systemd service是否是长期常驻的服务、生产任务crontab是否定时触发周期性任务实际工作中经常是组合使用开发调试阶段用 tmux验证没问题之后固化成 systemd 服务。4. 交给 systemd真正让服务活下来的方式如果你跑的东西不是跑一次就完的脚本而是要长期存在、挂了要自动重启、最好开机能自动起来的服务那nohup和 tmux 都不合适。它们要么没有重启能力要么需要人工维护一个会话。这时候应该用 systemd。4.1 为什么不推荐用 nohup 跑长期服务用nohup跑长期服务有几个绕不过去的问题没有自动重启。进程 OOM 被内核干掉、段错误崩了就真的没了没人拉它起来。没有依赖管理。它需要在数据库起来之后再启动nohup做不到只能靠手写sleep。资源限制不好设。想限制内存、CPU 亲和性得靠ulimit和taskset东拼西凑。状态不可观测。ps能看到进程在不在但看不到它启动了多久、重启过几次、上次为什么退出。systemd 把这些问题一次性解决。它本来就是现代 Linux 的初始化系统systemctl命令人人都会用学习成本主要在于写单元文件而单元文件的格式其实很简单。4.2 一个可以直接抄的 service 单元假设你有一个 Python 服务放在/opt/myapp启动命令是/opt/myapp/venv/bin/python /opt/myapp/main.py用哪个用户跑、工作目录在哪、环境变量怎么设都可以在单元文件里写清楚。新建文件/etc/systemd/system/myapp.service[Unit] DescriptionMy background data service Afternetwork-online.target Wantsnetwork-online.target [Service] Typesimple Userappuser Groupappuser WorkingDirectory/opt/myapp EnvironmentPYTHONUNBUFFERED1 EnvironmentENVprod ExecStart/opt/myapp/venv/bin/python /opt/myapp/main.py Restarton-failure RestartSec5 StandardOutputjournal StandardErrorjournal LimitNOFILE65535 [Install] WantedBymulti-user.target几个字段值得单独说一下Afternetwork-online.target配合Wants表示等网络真正就绪之后再启动。只写After不写Wants是不够的Wants才会把那个目标拉进来。WorkingDirectory一定要显式写。systemd 启动服务时的工作目录默认是/很多程序会在这个目录下找相对路径的配置文件找不到就报错退出这是最常见的手动跑没问题systemd 起不来的原因。Restarton-failure表示非正常退出退出码非零或被信号杀死才重启正常退出退出码 0不重启。如果是那种必须一直活着的服务可以改成always。RestartSec5防止崩溃循环把日志刷爆加个间隔很必要。EnvironmentPYTHONUNBUFFERED1是 Python 特有的让日志立即写出去而不是攒在缓冲区里这个后面还会讲。写完文件之后sudo systemctl daemon-reload # 让 systemd 重新读取单元文件 sudo systemctl enable myapp # 设置开机自启 sudo systemctl start myapp # 启动 sudo systemctl status myapp # 看状态daemon-reload这一步千万别忘。改了单元文件不 reloadsystemd 用的还是旧配置你会怀疑人生。4.3 日志走 journald 之后怎么看单元文件里如果写了StandardOutputjournal程序的输出会被 systemd 收走交给 journald 管理。这时候nohup.out之类的文件就不需要了查看用journalctl -u myapp -f # 实时跟踪 journalctl -u myapp --since today # 今天的所有日志 journalctl -u myapp -n 200 # 最近 200 行 journalctl -u myapp -p err # 只看 error 级别以上journald 默认会做日志轮转不会像nohup.out那样无限膨胀把磁盘塞满。不过要注意它的总量上限默认一般是磁盘的 10%可以在/etc/systemd/journald.conf里用SystemMaxUse调整。如果你更习惯把日志写到文件那就把StandardOutput和StandardError改成append:/var/log/myapp.log效果等同于自己重定向。两种方式各有好处走 journald 方便按时间过滤和统一管理走文件方便直接用tail、grep、awk处理。4.4 普通用户也能用 systemd不是所有人都有 root。好消息是 systemd 支持用户级服务systemctl --user就能操作单元文件放在~/.config/systemd/user/下。用法和系统级几乎一样mkdir -p ~/.config/systemd/user # 写好 myapp.service 放进去 systemctl --user daemon-reload systemctl --user enable --now myapp systemctl --user status myapp journalctl --user -u myapp -f这里有个坑用户级服务默认在该用户的所有会话都退出后会被停掉。也就是说你 SSH 登出服务就跟着停了。要让它一直活着需要开 lingeringloginctl enable-linger $USER这条命令执行一次即可之后即使你不登录用户级服务也会保持运行开机也会自动拉起。这是很多人在共享服务器上跑自己服务时的标准做法避免去动系统目录。5. 输出、日志和我以为它在跑的翻车现场进程活着不等于任务在正常推进。这一节讲几个我在实际使用中遇到过、并且看到别人反复遇到的坑基本都是围绕输出和验证这两件事。5.1 日志被缓冲吃掉明明在跑日志却一片空白最经典的场景nohup python app.py app.log 21 跑起来过了半小时tail app.log发现是空的但进程确实在CPU 也在跑。这时候的怀疑方向通常是程序卡住了其实大概率是输出缓冲。标准输出重定向到文件时C 标准库默认使用全缓冲通常是 4KB 或 8KB攒够一块才写一次磁盘。程序如果只在开头打印了几行启动中那点量根本填不满缓冲区你自然什么都看不到。解决办法分语言# 通用用 stdbuf 强制按行缓冲 stdbuf -oL -eL python app.py app.log 21 # Python 专用-u 参数关闭缓冲 python -u app.py app.log 21 # Python 里也可以主动 flush print(start, flushTrue)stdbuf -oL对 stdout 设行缓冲-eL对 stderr 设行缓冲两个都要写。不过stdbuf依赖LD_PRELOAD机制对静态链接的程序或者某些语言运行时可能无效所以更保险的是程序自身的开关比如 Java 可以用-Dfile.encoding之类配合日志框架配置Node 一般默认就是行缓冲。我在实际项目里养成的习惯是不管有没有用Python 脚本一律加-u。多打几个字符省掉一次排查。systemd 那边对应的就是EnvironmentPYTHONUNBUFFERED1。5.2 怎么确认进程真的在干活ps看进程在不在只是第一层。要确认它在干活有几个更靠谱的手段# 看进程的 CPU 时间是否在增长隔几秒看两次 ps -o pid,etime,time,pcpu,pmem,stat,cmd -p PID # 用 top 只看某个进程 top -p PID # 看它打开了哪些文件日志写到哪去了 ls -l /proc/PID/fd # 看它的工作目录和环境变量 ls -l /proc/PID/cwd tr \0 \n /proc/PID/environps输出里的TIME那一列是累计 CPU 时间隔十几秒看两次如果数值在涨说明它在消耗 CPU如果纹丝不动可能是卡在等 IO 或者死锁了。STAT列如果是D表示不可中断睡眠通常卡在磁盘 IO如果是S是正常等待如果是R是在跑如果是Z那就是僵尸进程需要查它的父进程。/proc/PID/fd这个目录特别有用。我遇到过好几次日志不知道写到哪去了的情况ls -l /proc/12345/fd/1一看指向的是某个早就删掉的文件程序还在往里写磁盘空间被一个看不见的文件占着df显示满但du找不到。这种情况需要重启进程才能释放。5.3 磁盘被日志撑爆一个凌晨三点的告警用nohup跑长期任务最容易忽略的就是日志积累。一个日志刷得快的程序一晚上写几十 GB 很正常。磁盘写满之后连锁反应是程序写日志失败可能直接崩溃数据库写不进去SSH 登录都可能因为无法写wtmp而失败你就彻底登不上去了只能走控制台。这类问题有两层防护第一层是控制日志本身。如果程序自己能设日志级别和轮转就最好不行的话就在日志文件层面做。用 logrotate 是标准做法写一个/etc/logrotate.d/myapp/var/log/myapp.log { daily rotate 7 compress delaycompress missingok notifempty copytruncate }这里copytruncate是关键。默认的 logrotate 是改名 通知程序重新打开文件但你的程序不一定支持重开用了copytruncate就是复制一份再清空原文件对程序透明。代价是在复制的瞬间可能丢一点点日志对于大多数场景可以接受。第二层是监控。用df -h定期看一眼或者写个简单的判断脚本挂到 crontab 里超过 85% 就发个通知给自己。别等到登不上服务器才发现问题。5.4 MobaXterm 侧的几个实用设置回到客户端这一侧MobaXterm 本身有几个设置和这个主题相关顺手提一下保存日志在 session 设置里可以开启保存终端输出到文件这样即使你忘了重定向客户端侧也留了一份记录。但要注意这只是客户端的记录进程本身的 stdout 还是指向服务器上的终端的不能替代服务端的重定向。保持连接在 SSH 设置里有个 keepalive / 发送空包的选项可以防止长时间无操作被网络设备断开。这只是减少掉线概率不是让程序不死两件事别混。断线自动重连有些版本支持重连重连之后你会看到一个新的 shell之前的前台命令是回不来的别指望它。真正稳的做法仍然是在服务器侧用前面说的方案把程序安顿好客户端断不断都无所谓。6. 几个真实场景下的组合拳与经验教训前面讲的是单个技术点这一节把几个真实场景串起来顺带说几个容易想岔的地方。6.1 关掉窗口不等于关掉服务器这是新手最常见的误解。关掉 MobaXterm 窗口关掉的只是一个远端的登录会话服务器本身好好地在机房里或者云上跑着其他用户的进程、系统服务、你之前用 systemd 起的服务全都不受影响。真正会受影响的只有挂在那个会话控制终端上的前台/后台进程组。理解这一点之后很多行为就顺了为什么同事的进程没被你的断线影响为什么systemctl起的服务关窗口没事为什么crontab里的任务从来不怕断线。它们共同的特点是没有把你的客户端终端作为控制终端。6.2 跳板机和多层 SSH 的坑如果中间隔了跳板机ssh -J或者手工两层登录要注意一点进程要安顿在最终执行它的那台机器上。很多人犯的错是在跳板机上敲了nohup然后登录到目标机器执行命令退出的时候只处理了跳板机那一层结果目标机器上的进程还是挂在目标机器的 pts 上照样会死。多层场景下的排查方法是在最终那台机器上确认进程的TTY列是不是?或者直接tmux ls看会话在不在。别图省事在跳板机上操作。还有一种情况是用了ssh -t强制分配终端或者某些自动化工具默认分配了 pty这时候nohup的效果可能被抵消需要额外加-n让 ssh 不读 stdin。6.3 定时任务和后台运行是两回事crontab和at这两个工具经常和后台运行混在一起谈其实它们解决的是不同问题。crontab解决的是什么时候执行at解决的是延迟到某个时间执行一次它们共同的特点是执行时没有控制终端所以天然不受断线影响。但要注意 cron 的执行环境非常简陋PATH可能只有/usr/bin:/bin没有你~/.bashrc里设的环境变量当前目录是用户家目录。所以 cron 里跑的脚本经常出现手动跑得好好的定时跑就报 command not found原因就在这里。习惯做法是在脚本开头显式设置PATH和需要的环境变量或者干脆用source加载一份自己的环境文件。另外cron 任务的输出默认会以邮件形式发给用户如果系统没配邮件这些输出就丢了。所以 cron 任务里最好也自己重定向输出比如 /var/log/myjob.log 21。6.4 我平时的选择习惯做了这么多年我现在基本形成了一套固定的判断逻辑说给你参考跑几十分钟到几个小时的脚本直接用nohup ... log 21 把 PID 记到文件里简单直接出问题也好查。需要看实时输出就tail -f log。需要交互式操作、或者调试阶段、或者需要多个窗口并行观察的开一个 tmux 会话tmux new -s 项目名在里面干活。命名有讲究用项目名而不是1、2一周之后回来还能知道哪个是哪个。确定要长期跑的服务直接写 systemd 单元文件enable加start之后就当它是个系统服务来管。日志走 journald需要持久化和过滤的时候再考虑写文件。有一点我想特别强调不要在 tmux 里跑你认为要长期存在的东西。tmux 会话会因为你误敲exit、误执行kill-session而消失服务器重启也会全部清空。它适合阶段性任务不适合常驻服务。分清楚这个边界能省掉很多次半夜爬起来重新拉服务的麻烦。最后分享一个我用了很久的小习惯给每个长任务单独建一个目录比如/data/jobs/20250612-etl/里面放run.pid、run.log、必要时放一份run.sh记录启动命令。三个月后回头查当时那个任务是怎么起的看目录就知道比翻 shell 历史记录靠谱得多。JOB/data/jobs/20250612-etl mkdir -p $JOB cd /opt/myapp nohup ./venv/bin/python main.py /dev/null $JOB/run.log 21 echo $! $JOB/run.pid四条命令一个习惯之后所有这个任务是谁起的、什么时候起的、日志在哪的问题都自动有了答案。
返回列表