Linux 下 nohup 记录日志实战:原理、用法与排坑指南

📅 发布时间:2026/10/19 2:25:52
Linux 下 nohup 记录日志实战:原理、用法与排坑指南
前段时间有朋友从某家公司的技术分享会上带回来一个问题他们在生产环境启动一个数据同步脚本终端一关进程就没了日志也找不到了。后来排查下来问题就出在启动命令上——没有用 nohup也没有把输出写到文件里。这话题说起来基础但很多人真到了要用的时候会在一堆细节上栽跟头。今天就把“Linux 里用 nohup 记录日志”这件事从头到尾拆一遍从原理到实操再到排坑一次性说透。1. 先搞清楚 nohup 到底在解决什么问题1.1 为什么终端一关进程就跟着退很多人第一次遇到进程“莫名其妙消失”第一反应是程序崩溃了其实大多数时候不是。你从 SSH 登进服务器敲下./run.sh这个脚本就成了当前终端会话里的一个前台进程。终端窗口关闭时系统会向这个会话的进程组发送挂断信号进程收到的信号如果没做特殊处理默认动作就是终止退出。在 Linux 下终端关闭导致进程退出的链路很清晰终端会话结束时内核向会话首进程发送 SIGHUP 信号前台进程组里的所有进程都会收到这个信号。信号名字里的 HUP 就是 hang up 的缩写字面意思就是“挂断电话”。可以这么理解终端是电话线进程是正在通话的人电话线断了通话里的人默认就不再继续说话了。nohup 这个词也是 no hang up 的缩写它的作用就是让进程忽略 SIGHUP 信号。信号被忽略后终端断了进程照样跑日志照样写这就是它解决的核心问题。1.2 “后台运行”并不等于“断开终端还能跑”新手常犯的一个混淆是以为加上把命令放到后台就万事大吉。的作用只是把进程放到后台调度让当前 shell 不用等它结束可以继续敲别的命令。但如果终端会话关闭SIGHUP 照样会发给这个后台进程没有 nohup 保护的话它一样会退出。nohup和是一对常见组合nohup 负责“忽略挂断信号”负责“放到后台执行”。单独用任何一个都不完整前者不放到后台的话会占住终端后者不忽略挂断信号的话关终端还是会死。这也是很多脚本写nohup command 的原因两个行为同时生效进程才能真正“脱离开终端”跑。1.3 进程的输出默认去了哪里进程内部有三个标准数据通道标准输入、标准输出、标准错误。终端里直接跑一条命令时标准输出和标准错误都指向屏幕所以你看到的信息全是“打印在终端上”的。终端关了屏幕没了这些输出也就丢了。nohup 命令在这里做了一件很贴心的事如果启动命令时没有手动重定向标准输出nohup 会把标准输出自动写到当前目录下一个叫nohup.out的文件里同时把标准错误也一并追加进去。启动时屏幕上出现的那行提示nohup: ignoring input and appending output to nohup.out就是在告诉你输入被忽略输出进了 nohup.out。这就是日志记录的最初形态也是很多人第一次接触 nohup 时看到的“自动日志文件”。但自动归自动实际生产环境里直接把日志丢给 nohup.out 并不是好做法原因后面细说。2. nohup 日志记录的核心用法与重定向细节2.1 基础命令格式命令格式其实很简单nohup 命令 [参数] [重定向] 不写重定向时输出默认进 nohup.out。但这里有第一个坑nohup.out 的位置取决于你启动命令时所在的目录。你在/home/user/下启动它就写在/home/user/nohup.out在/tmp/下启动就写在/tmp/下。如果不注意当前目录后面找半天日志找不到这种情况我真见过不少。所以规范做法是显式指定输出文件而不是依赖默认的 nohup.outnohup ./sync_data.sh /var/log/sync_data.log 21 这条命令做了三件事忽略挂断信号、把标准输出写到指定日志文件、把标准错误也指向同一个文件然后放到后台执行。日志路径完全可控后面查日志、做轮转都方便。2.2 标准错误和标准输出为什么要同时重定向很多人不理解21是什么含义简单拆一下1代表标准输出默认指向屏幕2代表标准错误默认也指向屏幕是重定向符号 文件表示把标准输出写入文件21的意思是把标准错误重定向到“当前标准输出所指向的地方”所以 app.log 21合起来的效果就是标准输出写进 app.log标准错误也写进 app.log。两条通道合并到同一个日志文件里。为什么非要写21因为很多脚本调试时讲不清错误到底去了哪。你不重定向标准错误的话程序报错信息仍然打到屏幕上而屏幕随终端关闭后这些内容就彻底丢了。日志文件里只看到正常的输出查不到异常堆栈排查问题的时候非常被动。这里有个细节不少人都理解反了21的写法是固定的两个符号的先后顺序不能随便改。写成21 app.log和写成 app.log 21结果完全不同。前者的顺序是先把标准错误指向“当前的的标准输出”此时还是屏幕再把标准输出重定向到文件最终结果变成错误打到屏幕输出写进文件并没有达到合并日志的目的。正确写法一定是把21放在之后。2.3 追加还是覆盖日志不能“每次启动都从头开始”重定向符号是覆盖写入每次启动都会把旧日志清空重写。这在有些场景下没问题但做长期任务监控时历史日志是有价值的建议用追加写入nohup ./task.sh /var/log/task.log 21 表示以追加方式写入旧日志不会丢。这个区别特别适合那种“会多次启动”的任务比如每天跑一次的定时脚本。覆盖写入会把前一天的关键记录冲掉查问题时缺了上下文很麻烦。另外要区分一个场景如果脚本自己内部已经处理了日志逻辑比如 Java 程序通过 log4j 写文件Python 脚本用 logging 模块落盘那外面 nohup 的重定向主要接管的是程序“额外打出来的那部分输出”。这种情况下更要注意先确认程序本身的日志路径在哪别把 nohup 的日志文件当成唯一日志来源。2.4 有些人把输出丢进 /dev/null什么时候可以这么干有一种写法也很常见nohup ./daemon.sh /dev/null 21 /dev/null是 Linux 里的“黑洞设备”写到里面的数据直接丢弃。有些系统服务进程本身已经把日志写到自己的文件里了外部输出没有保留价值这么写能让环境干净些也避免 nohup.out 无意义增长。什么情况下适合丢黑洞比如进程本身已经用专业日志库记录日志、而且程序稳定运行不需要靠标准输出辅助排查时。什么情况下别丢比如脚本没做内部日志、全靠输出定位问题的时候把输出丢进黑洞等于把故障线索全丢了。给个个人建议拿不准的时候就保留日志。磁盘空间通常不是问题日志没了才是真问题。3. 完整实操启动后台任务并管理日志3.1 准备一个可运行的实验脚本为了把整个流程走通这里用一个简单的模拟脚本演示。假设脚本内容如下#!/bin/bash echo 脚本启动时间$(date) for i in $(seq 1 100); do echo 第 ${i} 次循环$(date) sleep 2 done echo 脚本结束时间$(date)脚本每两秒打印一行记录总共跑 200 秒左右足够演示日志的实时写入和后台运行。实际生产中可以替换成你自己的同步脚本、数据分析任务或者应用启动命令。3.2 使用 nohup 启动并确认日志文件进入脚本所在目录执行cd /opt/demo/ nohup bash test.sh /opt/demo/test.log 21 执行后会返回一个进程号比如[1] 12345。稍等几秒查看日志文件内容tail -f /opt/demo/test.log你会看到日志按两秒一行的频率持续刷新。这里tail -f是实时跟踪文件内容的命令日志有任何新行都会立刻显示出来排查任务是否正常运行非常高效。同时用进程管理命令确认任务还活着ps -ef | grep test.sh | grep -v grep能看到 PID 为 12345 的 bash test.sh 进程还在运行就说明 nohup 生效了。3.3 验证终端断开后进程仍然存活并写日志这一步建议实际操作一下。你从电脑 SSH 登录服务器启动上面的 nohup 任务后直接退出 SSH 会话过几分钟再重新登录。查两件事进程在不在日志有没有继续更新。ps -ef | grep test.sh | grep -v grep tail -n 20 /opt/demo/test.log进程没消失、日志时间戳在你退出期间仍在持续前进就说明进程彻底脱离了终端会话独立运行。这套验证方式同样适用于生产环境属于“上线后必做的自检”。3.4 记录 PID 并掌握停进程的正确方式nohup 启动的任务没有挂在某个终端上不能用 CtrlC 直接终止这个后面详聊。停进程的方式是通过 PID 发信号kill 12345如果进程有业务逻辑需要优雅退出可以先尝试发 TERM 信号让它自己清理再升级到强制结束信号kill -9 12345生产环境里建议把启动命令写成一个 start.sh把进程 PID 记录下来#!/bin/bash nohup java -jar app.jar app.log 21 echo $! app.pid$!在 shell 里表示“最近一个后台进程的 PID”写进 pid 文件后后续 stop 脚本可以这样读kill $(cat app.pid)这套“启动脚本记 PID 停止脚本读 PID”的模式比每次临时找进程号靠谱得多尤其适合多实例部署的环境。3.5 日志文件拿不到内容或者打不开时的先手排查有时候 nohup 启动后日志文件迟迟不出现或者文件存在但里面是空的。排查顺序是这样的第一先确认命令本身有没有执行成功。如果命令路径写错、脚本没执行权限是不会生成日志的。再强调一遍nohup: ignoring input and appending output to nohup.out出现说明 nohup 已经启动但它只能接管“启动成功的进程”命令本身就报错的话日志自然没有内容。第二检查当前目录。你没显式重定向时默认写 nohup.out这个文件生成在“启动命令所在的目录”不是“脚本所在的目录”。这两个目录不一定一样命令行里cd到哪nohup.out 就建在哪。第三日志文件存在但内容不更新多半是程序输出有缓冲。这个问题下一节详细展开因为它是非常高频的坑。3.6 用定时轮转避免日志文件无限膨胀日志文件无脑增长是后台任务长期运行的另一个隐患。写满磁盘后程序可能就无法正常写日志甚至直接影响业务。Linux 自带的 logrotate 工具可以做按大小或按天轮转例如每天切割保留 7 份/opt/demo/test.log { daily rotate 7 compress delaycompress missingok notifempty }把这段配置放到/etc/logrotate.d/demo下logrotate 会每天自动把 test.log 改名成 test.log.1再生成一个新的 test.log。进程写入使用的是文件描述符文件被移动之后新写入会继续追加到新创建的 test.log 里这个机制和 Java 的 logback、Python 的 TimedRotatingFileHandler 思路一致。没有 logrotate 的临时方案可以手动截断 /opt/demo/test.log这条命令能把文件内容清空但文件本身不动进程写入不中断。注意别用rm文件被删后进程持有的句柄仍然在写那块“已删除的 inode”磁盘空间不会释放而且会越写越暗黑。4. 日志记录常见问题与排查技巧实录4.1 高频故障速查表整理一份问题排查表遇到类似情况可以直接对着查现象可能原因解决办法启动提示nohup: ignoring input and appending output to nohup.out未显式重定向日志在 nohup.out启动时加 自定义日志 21日志文件根本没生成脚本本身启动失败或当前目录不对先手动运行脚本看报错再核对路径日志文件生成但不更新程序输出有缓冲改用stdbuf -oL或程序内 flush关掉终端后进程消失用了没用 nohup或 nohup 未生效确认启动命令以 nohup 开头日志写满磁盘文件未轮转配置 logrotate 或定时截断标准错误没写进日志重定向缺少21改为 文件 21进程还在但日志不追加文件被删除但句柄仍在写重启进程或避免 rm 日志文件这几类问题覆盖了我这些年遇到的大部分 nohup 日志相关故障每条都有现实案例对应。4.2 日志不实时刷新程序输出缓冲问题这是最有迷惑性的一个问题。线上跑了一个 Python/Java/C 程序nohup 命令启动没问题进程也在但tail -f看不到输出隔了很久才突然刷出来一大段。原因是程序自身有输出缓冲。很多语言在输出到“文件”或“管道”时默认开启全缓冲而不是行缓冲。全缓冲意味着缓冲区没满或者程序没正常退出时内容不会及时落盘。终端上的行缓冲则是一遇到换行就输出所以你在前台跑时看到的是实时打印一重定向到文件就变“堵塞”了。解决办法分语言看Python 脚本启动时加-u参数python -u script.py可以强制无缓冲输出Java 程序日志框架通常会定时刷盘不用额外处理如果是 System.out可以用System.out.flush()C 程序用stdbuf -oL命令改变缓冲策略stdbuf -oL ./a.out表示按行输出实际生产里如果程序有自己的日志系统log4j、logback、logging 模块nohup 捕获的输出只是辅助手段缓冲问题影响有限。但如果程序只靠标准输出打日志这个坑必须提前处理好。4.3 前台启动的 nohup 为什么还能被 CtrlC 打断有人试过不加直接跑nohup command发现 CtrlC 还能把进程结束于是怀疑 nohup 没生效。其实是理解偏差nohup 忽略的是挂断信号不是中断信号。终端按 CtrlC 发送的是 INT 信号nohup 不负责处理这个。要真正让进程脱离终端又不被打断必须nohup command 组合使用。只 nopause 不加进程仍然占用当前终端你一旦关掉终端SIGHUP 会被忽略进程活下来但你在终端里按 CtrlC 它就没了。这个区别在生产环境的启动脚本里经常出事故有人在测试环境前台启动一切正常上线时把命令拷贝到 start.sh忘了加结果部署后终端会话一退出虽然进程还活着但运维想终止任务时发现它挂在那个终端上无法正常管理找起来非常费劲。4.4 停机时日志文件最后一行不完整很常见的一个现象进程被 kill 后日志文件的最后一行只有半截没有换行符。比如输出写了一半就收到强制结束信号。这通常是程序还在缓冲没写入文件kill -9直接打断导致缓冲区数据丢失。解决思路有两个层面程序层面用日志库时设置 autoflush或定期刷盘。运维层面优先用kill发送 TERM 信号而不是kill -9给程序预留机会处理未写完的内容。如果任务对日志完整性要求很高比如银行交易类应用还得在应用层做事务日志而不是依赖标准输出流。4.5 nohup 启动后命令却立刻退出如何排查有些脚本 nohup 启动后日志文件生成了一下进程马上没了。这时先去查日志如果里面记录了一段报错信息说明脚本启动后自己退出。如果日志只有启动行然后就没了可能是脚本执行被环境变量、依赖条件卡住。这类问题唯一的有效路径就是“先前台跑排除疑问”。直接在终端执行同样的命令不叠加 nohup 和看它在屏幕上输出什么、报什么错、能跑多久。前台能稳定运行再套 nohup这是排查这类问题的基本顺序。跳过这一步直接猜原因十有八九浪费时间。顺手提一个常见细节nohup后面的命令如果是一个需要交互输入的程序它会在没有任何输入来源的环境下因为读不到输入而报错退出。nohup 默认忽略输入就是为了避免进程因为从空终端读输入而挂起。5. 结合团队实际场景的落地建议5.1 nohup 适合什么场景不适合什么场景用 nohup 最舒服的场景是“临时跑一个长任务”比如数据迁移、压测脚本、批处理任务跑完就走不需要常驻监控。这类任务用 nohup 启动成本最低一行命令就解决问题。不适合的场景是“核心业务服务”。核心服务需要开机自启、崩溃自动拉起、集中日志管理这些是 systemd 或者容器编排工具的活。nohup 服务挂了不会自己恢复也没有依赖管理日志散落在各个目录不适合承担长期稳定运行的职责。可以这么判断任务是“一次性”还是“常驻型”一次性任务优先 nohup常驻服务交给 systemd。5.2 日志文件命名规范与应用目录隔离团队协作时日志命名和路径如果没有规范排查问题就是灾难。我见过的统一做法是每个应用建独立日志目录/data/logs/应用名/日志文件名关联任务名和日期task-$(date %Y%m%d).log启动脚本中固定服务名禁止裸启动命令比如nohup ./run_task.sh /data/logs/task/task-$(date %Y%m%d).log 21 日志按天分文件配合 logrotate 做轮转整套管理下来清晰很多。这个规范不在于用什么工具而在于“提前定好谁写在哪儿、写多久、写完怎么处理”避免人人和任务之间互相覆盖日志文件。5.3 结合 tmux 做“既要后台又要可交互”的场景还有一种需求经常被提出来希望进程在后台运行但有时候想切进来看一眼甚至临时敲点命令。nohup 只能把进程推出去做不了“随时交互”。这种情况建议用 tmux。tmux 可以理解为一个“虚拟终端会话管理器”。会话在后台保持你随时能重新接入里面有多个窗口可以看到进程当前的状态即使你断开连接会话里的进程也不会退出。对比一下工具后台运行日志文件随时进入交互适用场景nohup支持支持不支持临时长任务tmux支持依赖内部命令支持需要交互式操作的后台任务systemd支持journalctl不支持常驻服务管理如果你经常和数据开发、算法工程打交道会发现 tmux 是另一个常用工具模型训练动辄跑十几个小时跑完还能切回去看输出比 nohup 体验好不少。但它的日志记录依赖会话内工具不像 nohup 一句话就能把输出落盘。5.4 一个相对完整的启动脚本模板结合上面的经验给一套可以参考的脚本结构。任务启动脚本 start.sh#!/bin/bash APP_NAMEsync_data LOG_DIR/data/logs/${APP_NAME} PID_FILE/var/run/${APP_NAME}.pid mkdir -p ${LOG_DIR} LOG_FILE${LOG_DIR}/${APP_NAME}-$(date %Y%m%d).log nohup ./run_task.sh ${LOG_FILE} 21 echo $! ${PID_FILE} echo 进程已启动PID: $(cat ${PID_FILE}) echo 日志文件: ${LOG_FILE}停止脚本 stop.sh#!/bin/bash PID_FILE/var/run/sync_data.pid if [ -f ${PID_FILE} ]; then PID$(cat ${PID_FILE}) kill ${PID} 2/dev/null echo 已发送终止信号给进程 ${PID} rm -f ${PID_FILE} else echo 未找到 PID 文件请手动确认进程状态 fi脚本的逻辑不复杂核心价值在于把日志路径、PID 记录、启动与停止目录等容易出错的细节固化下来避免每次启动时靠人脑记。6. 最后一些个人实操体会用了这么多年 nohup最大的体会就是“日志路径一定要显式指定”。默认 nohup.out 虽方便但是在当前目录随机生成时间一久根本找不到哪份日志对应哪个任务尤其是服务器上任务多的时候会乱成一团。固定目录、按日命名、启动脚本记录 PID这些看起来“多写几行”的操作能让排查问题的成本下降一个量级。还有一个小技巧值得分享启动完成以后第一件事就是用tail -f看一眼日志是否正常滚动确认进程真的进入了工作状态而不是等到第二天发现任务早就挂了却毫无痕迹。这个习惯每多坚持一次就能少踩一次“启动时没报错但其实没跑起来”的坑。关于日志文件也提醒一句重定向的顺序、追加与覆盖的选择、缓冲导致的延迟这些都是小事但恰恰是小问题积累起来才让本来简单的 nohup 使用变得棘手。把这些细节记录到团队的手册里新人接手时照着做能少走不少弯路。