nohup 命令详解与参数实测(Ubuntu 22.04)
逐项实测 Ubuntu 22.04 的 nohup(GNU coreutils 8.32):后台运行与 nohup.out 捕获、显式重定向、stdin/stdout 三种终端状态的行为差异、抗 SIGHUP 对照实证、父 shell 退出后 PPID 变 1、对已运行进程无效,附记录 PID、安全停止等实战场景与常见问题排查。
nohup 命令详解与参数实测(Ubuntu 22.04)
目标:全面掌握 nohup 的每一个常用参数与行为——基本后台运行、nohup.out 输出捕获、显式重定向、stdin/stdout 三种终端状态的行为差异、抗 SIGHUP 对照实证、父 shell 退出后的进程归属、对已运行进程无效、
--选项结束符与--help/--version,全部命令与回显已在 Ubuntu 22.04(GNU coreutils 8.32)实测通过。
一、环境信息
本篇所有命令与回显均在以下环境实测采集,读者在同等环境(Ubuntu 22.04 服务器、GNU coreutils 8.32)执行应得到一致结果。
- 操作系统:Ubuntu 22.04.5 LTS(
PRETTY_NAME="Ubuntu 22.04.5 LTS") - 架构:x86_64
- nohup 版本:GNU coreutils 8.32(
nohup (GNU coreutils) 8.32) - nohup 路径:
/usr/bin/nohup - 执行身份:root
- 演示数据目录:
/root/demo/nohup/ - 辅助验证工具:
ps(procps)、grep、ls
💡 技术拓展:让进程"不随会话退出而死"不止 nohup 一种做法,常见手段横向对比——
手段 原理 适用场景 nohup 启动时把 SIGHUP 设为忽略(SIG_IGN),配合 &后台运行登录会话/SSH 断开不中断长任务,最通用 &(单用)仅放入后台作业,不改变信号处置 只想后台化,不关心会话断开 disown(bash 内置) 把作业从 shell 作业表移除,shell 退出时不再向它发 SIGHUP 对已启动、未加 nohup 的后台作业"事后补救" setsid 让进程新建会话脱离控制终端,从源头不收 HUP 守护进程化、彻底脱离终端 screen / tmux 终端复用器,会话可 detach/attach 需要随时回到交互现场的长会话 本文主角是 nohup:它无需额外安装、一条命令生效,是服务器运维中最常用的选择。
二、命令概览
2.1 语法与测试数据
nohup 的定位是:运行指定命令,并让它忽略挂断信号(SIGHUP),通常与 & 组合放入后台,用于 SSH 断开、终端关闭后任务不中断的场景。语法与参数极少:
nohup COMMAND [ARG]... # 以忽略 SIGHUP 的方式运行 COMMAND
nohup OPTION # OPTION 仅支持 --help 与 --version
nohup 自身没有业务参数,它的全部"参数"体现在标准输入/输出/错误的重定向规则上(见 2.2 与第三章)。先准备演示数据:一个会循环输出 4 行的演示脚本 task.sh。
mkdir -p /root/demo/nohup
cat > /root/demo/nohup/task.sh <<'EOF'
#!/usr/bin/env bash
for i in 1 2 3; do
echo "task running: line $i"
sleep 1
done
echo "task finished"
EOF
chmod +x /root/demo/nohup/task.sh
脚本内容确认如下(cat 回显为实测):
$ cat /root/demo/nohup/task.sh
#!/usr/bin/env bash
for i in 1 2 3; do
echo "task running: line $i"
sleep 1
done
echo "task finished"
2.2 --help 全量输出(逐行照抄)
--help 是 nohup 仅有的两个参数之一,输出里包含它全部三条重定向规则,是理解第三章所有实测现象的钥匙:
$ nohup --help; echo rc=$?
Usage: nohup COMMAND [ARG]...
or: nohup OPTION
Run COMMAND, ignoring hangup signals.
--help display this help and exit
--version output version information and exit
If standard input is a terminal, redirect it from an unreadable file.
If standard output is a terminal, append output to 'nohup.out' if possible,
'$HOME/nohup.out' otherwise.
If standard error is a terminal, redirect it to standard output.
To save output to FILE, use 'nohup COMMAND > FILE'.
NOTE: your shell may have its own version of nohup, which usually supersedes
the version described here. Please refer to your shell's documentation
for details about the options it supports.
GNU coreutils online help: <https://www.gnu.org/software/coreutils/>
Report any translation bugs to <https://translationproject.org/team/>
Full documentation <https://www.gnu.org/software/coreutils/nohup>
or available locally via: info '(coreutils) nohup invocation'
rc=0
逐段解读这份 help(建议记牢中间三条规则,第三章的现象全部由此推出):
| 规则 | 含义 | 实测体现 |
|---|---|---|
| stdin 是终端 | 重定向自一个不可读文件(/dev/null),命令读不到输入 | 3.4 节 cat 立即退出 |
| stdout 是终端 | 输出追加到当前目录 nohup.out(不行则 $HOME/nohup.out),并打印提示 | 3.1 节 appending 提示 |
| stderr 是终端 | 重定向到 stdout 的去向(与 stdout 同目标) | 3.4 节管道场景提示语 |
| 想自选输出文件 | 显式 nohup COMMAND > FILE | 3.3 节不再生成 nohup.out |
💡 说明:help 中的 NOTE 提示"你的 shell 可能有自己的 nohup 版本"——bash 并没有内置 nohup(
type nohup返回/usr/bin/nohup),但 csh/tcsh 等存在内置实现,行为略有差异;在 bash 中执行的始终是这里的 coreutils 版本。
2.3 版本信息
$ which nohup
/usr/bin/nohup
$ nohup --version | head -2
nohup (GNU coreutils) 8.32
Copyright (C) 2020 Free Software Foundation, Inc.
版本号 8.32 与 Ubuntu 22.04 仓库中 coreutils 的打包版本一致;不同发行版上回显格式相同,仅版本数字可能不同。
三、参数逐项实测
nohup 只有 --help/--version 两个选项参数,其余行为都由"标准输入/输出/错误是否为终端"自动决定。本章按行为维度逐项实测,每一条现象都能对应 2.2 节的三条规则。
3.1 基本用法:nohup sleep 100 &(默认追加输出到 nohup.out)
本节验证最经典用法:nohup + & 放入后台,观察提示语与 nohup.out 的生成。
$ nohup sleep 100 & sleep 0.5
nohup: appending output to 'nohup.out'
$ ls -l /root/demo/nohup/nohup.out
-rw------- 1 root root 0 Oct 15 10:11 /root/demo/nohup/nohup.out
$ wc -c /root/demo/nohup/nohup.out
0 /root/demo/nohup/nohup.out
三个关键观察:
- 提示语:
nohup: appending output to 'nohup.out'——stdout 是终端,nohup 把它重定向为追加写当前目录的nohup.out,并打印这条提示。 - 文件权限:
-rw-------(600),只有当前用户可读写,符合安全默认。 - 初始为空:
wc -c显示 0 字节,sleep本来就不产生输出。
💡 要点:提示语是
appending output to 'nohup.out'而不是完整的ignoring input and appending output to 'nohup.out'。原因是&放入后台时,shell 已先把该作业的 stdin 重定向为 /dev/null,nohup 检测到 stdin 已不是终端,就只处理 stdout(对应 2.2 规则第 1、2 条的组合)。前台运行时才会出现完整提示语,见 3.9 节。
3.2 nohup.out 内容验证
本节验证"后台任务的 stdout 确实被 nohup.out 捕获":后台运行 3.1 节准备的 task.sh(它每秒输出一行),等待结束后读取 nohup.out。
$ nohup /root/demo/nohup/task.sh & sleep 0.3
nohup: appending output to 'nohup.out'
$ cat /root/demo/nohup/nohup.out
task running: line 1
task running: line 2
task running: line 3
task finished
$ ps -o pid,ppid,stat,cmd -p 18178; echo rc=$?
PID PPID STAT CMD
rc=1
结果验证闭环:
nohup.out中逐行出现了脚本的 4 行输出——后台任务的 stdout 在任务自己不知情的情况下被完整落盘;ps查询该 PID 已无结果(rc=1),任务正常结束,输出全部留在nohup.out中,这就是"断了终端也不丢输出"的意义。
3.3 显式重定向:不再生成 nohup.out
按 2.2 节最后一条提示 To save output to FILE, use 'nohup COMMAND > FILE',显式重定向后 nohup 不再接管 stdout,也就不会创建 nohup.out。这是生产环境推荐写法(日志名可控、可接 logrotate)。
$ nohup /root/demo/nohup/task.sh > /root/demo/nohup/task.log 2>&1 & sleep 0.3
$ ls -l /root/demo/nohup/
total 8
-rw-r--r-- 1 root root 77 Oct 15 10:11 task.log
-rwxr-xr-x 1 root root 106 Oct 15 10:11 task.sh
$ cat /root/demo/nohup/task.log
task running: line 1
task running: line 2
task running: line 3
task finished
验证结果:目录里没有 nohup.out(对比 3.1/3.2),输出落在指定的 task.log,且 2>&1 把 stderr 也并入了同一文件(连 nohup 自己的提示都不会打印——stdout 已不是终端,三条规则全部不触发)。
💡 要点:
> task.log 2>&1的顺序不能颠倒成2>&1 > task.log——前者是"stdout 到文件、stderr 跟随 stdout",后者 stderr 仍指向旧终端。nohup 场景下错误顺序会导致错误信息丢失。
3.4 stdin 忽略行为(ignoring input)
本节用三组对照实测 stdin/stdout 不同状态下的行为差异,全部对应 2.2 节三条规则。
第一组:stdin 是管道、stdout 是终端——管道输入被保留,stdout 照样进 nohup.out:
$ echo hello | nohup cat; echo rc=$?
nohup: appending output to 'nohup.out'
rc=0
$ cat /root/demo/nohup/nohup.out
hello
cat 正常读到了管道里的 hello 并写入 nohup.out——nohup 只在 stdin 是终端时才丢弃输入,管道等真实输入源不受影响。
第二组:后台运行一个等待输入的 cat——shell 先把后台作业的 stdin 置为 /dev/null,cat 立即读到 EOF 退出,输出文件为空(nohup 没打印任何提示,因为三条重定向的触发条件都不满足):
$ nohup cat > /root/demo/nohup/cat.out 2>&1 & sleep 1
$ ps -o pid,cmd -p 18198; echo rc=$?
PID CMD
rc=1
$ cat /root/demo/nohup/cat.out
第三组:stdin 是终端、stdout 是管道——不生成 nohup.out,stderr 被并入 stdout(管道),提示语变为 ignoring input and redirecting stderr to stdout:
$ nohup sleep 2 | cat; echo rc=$?
nohup: ignoring input and redirecting stderr to stdout
rc=0
$ ls -l /root/demo/nohup/nohup.out; echo rc=$?
ls: cannot access '/root/demo/nohup/nohup.out': No such file or directory
rc=2
把四组实测(含 3.1、3.9)汇总成一张行为对照表:
| 场景(实测命令形态) | stdin 实际状态 | stdout 实际状态 | 提示语 | nohup.out |
|---|---|---|---|---|
nohup sleep 100 & | /dev/null(shell 对后台作业的默认) | 终端 | appending output to 'nohup.out' | 生成 |
nohup /path/task.sh & | /dev/null | 终端 | appending output to 'nohup.out' | 生成 |
echo hello | nohup cat | 管道 | 终端 | appending output to 'nohup.out' | 生成 |
nohup sleep 2 | cat | 终端 | 管道 | ignoring input and redirecting stderr to stdout | 不生成 |
nohup -- uname -r(前台) | 终端 | 终端 | ignoring input and appending output to 'nohup.out' | 生成 |
nohup task.sh > task.log 2>&1 & | /dev/null | task.log | 无提示 | 不生成 |
💡 要点:表里的规律一句话——nohup 只补"终端"的缺:stdin 是终端才丢弃输入、stdout 是终端才写 nohup.out、stderr 是终端才并到 stdout;三处都不是终端时(如已显式重定向)nohup 完全"隐身"。
3.5 抗 SIGHUP 实证(kill -HUP 对照实验)
nohup 的核心承诺是"忽略 SIGHUP"。本节用两个同源对照进程直接发 HUP 信号验证:一个普通后台 sleep,一个 nohup 启动的 sleep。先看 /proc/<PID>/status 里的 SigIgn(被忽略信号掩码):
$ nohup sleep 1000 >/dev/null 2>&1 & sleep 0.3
$ grep SigIgn /proc/18206/status; grep SigIgn /proc/18208/status
SigIgn: 0000000000000006
SigIgn: 0000000000000007
$ kill -HUP 18206; sleep 0.5; ps -o pid,ppid,stat,cmd -p 18206; echo rc=$?
/root/nohup-test.sh: line 4: 18206 Hangup sleep 1000 > /dev/null 2>&1
PID PPID STAT CMD
rc=1
$ kill -HUP 18208; sleep 0.5; ps -o pid,ppid,stat,cmd -p 18208; echo rc=$?
PID PPID STAT CMD
18208 18157 S+ sleep 1000
rc=0
逐层解读这个决定性证据:
- SigIgn 掩码:信号掩码按位编号,bit0=SIGHUP、bit1=SIGINT、bit2=SIGQUIT。普通后台进程
0x6(只忽略 INT/QUIT——shell 对后台作业的默认处置),nohup 进程0x7(多出 bit0,即 SIGHUP 被忽略)。 - 普通进程收到 HUP:立即死亡——bash 打出
18206 Hangup通知,ps查无此进程,rc=1。 - nohup 进程收到 HUP:安然无恙,
STAT=S+仍在运行,rc=0。
| 对照项 | SigIgn | 收到 SIGHUP 后 |
|---|---|---|
普通 sleep 1000 & | 0000000000000006(INT/QUIT) | 进程终止(Hangup) |
nohup sleep 1000 & | 0000000000000007(HUP/INT/QUIT) | 继续运行 |
💡 要点:这就是 3.7 节对照实验的理论根基——nohup 的"抗挂断"不是玄学,而是启动瞬间把 SIGHUP 设为 SIG_IGN;系统向它投递 HUP 时内核直接丢弃。
3.6 父 shell 退出后:nohup 进程被 1 号进程收养(PPID=1)
本节模拟"会话里启动后台任务后 shell 退出":用 bash -c 起一个即将退出的父 shell,在其中用 nohup 拉起长任务,随后观察进程归属。
$ bash -c 'nohup sleep 100 >/dev/null 2>&1 & echo $!'
18217
$ ps -o pid,ppid,cmd -p 18217
PID PPID CMD
18217 1 sleep 100
结果:父 shell 退出后,sleep 100 仍在运行,PPID 已变为 1——孤儿进程被 1 号进程收养,继续存活。echo $! 输出的 18217 就是后台任务的 PID,运维中常把这个号记入文件以便后续 kill(见 4.2 节实战)。
3.7 对照:不加 nohup 的后台作业(如实记录)
作为对照,同样的方式但不加 nohup,观察进程是否存活:
$ bash -c 'sleep 100 >/dev/null 2>&1 & echo $!'
18220
$ ps -o pid,ppid,cmd -p 18220; echo rc=$?
PID PPID CMD
18220 1 sleep 100
rc=0
如实记录实测结果:不加 nohup 的后台作业在本场景下同样存活,PPID 同样变成 1。原因:bash -c 属于非交互 shell,退出时默认并不向后台作业发送 SIGHUP,作业只是被 1 号进程收养而已。因此:
- 在"非交互 shell 脚本退出"场景,nohup 与否结果相同(都被收养存活);
- nohup 的真正价值体现在SIGHUP 真的会被发出的场景:关闭终端/SSH 断开(终端驱动向前台进程组发 HUP)、交互式登录 shell 开启
huponexit后退出等——此时无 nohup 的作业会像 3.5 节的对照组一样被 HUP 杀死,而 nohup 作业免疫。3.5 节的 kill -HUP 实验正是对这些场景的等效实证。
⚠️ 注意:不要把"PPID=1 还活着"当成 nohup 生效的证据——判断 nohup 是否生效要看
SigIgn掩码(3.5 节)或直接发 HUP 验证。
3.8 nohup 无法作用于已运行进程
nohup 只能"从启动起"忽略 SIGHUP,没有把已有进程纳入保护的选项。用不存在的 -p 选项验证(也顺带证明 nohup 除 --help/--version 外无任何选项):
$ nohup -p 1; echo rc=$?
nohup: invalid option -- 'p'
Try 'nohup --help' for more information.
rc=125
$ nohup --help | head -3
Usage: nohup COMMAND [ARG]...
or: nohup OPTION
Run COMMAND, ignoring hangup signals.
rc=125 表示命令本身用法错误。结论与替代方案:
- nohup 只对新启动的进程设置 SIGHUP 忽略,启动后信号处置即固化;
- 对已运行的后台作业,补救手段是 bash 内置
disown(把作业移出作业表,shell 退出时不再向它发 HUP);想彻底脱离终端可改用setsid重启进程(见第一章拓展表)。
3.9 -- 选项结束符
nohup 支持 -- 表示后续参数全部按命令本身解析(防止以 - 开头的命令被误认为选项)。这里顺带观察前台运行时的完整提示语(stdin 与 stdout 都是终端):
$ nohup -- uname -r; sleep 1; cat /root/demo/nohup/nohup.out; rm -f /root/demo/nohup/nohup.out
nohup: ignoring input and appending output to 'nohup.out'
6.8.0-138-generic
与 3.1 节对比:前台运行时 stdin 仍是终端,nohup 把它转向 /dev/null,于是提示语带上 ignoring input 前缀;uname -r 的输出则被写入 nohup.out(cat 读出的 6.8.0-138-generic 即内核版本),最后一步已将演示文件清理。
四、实战示例
以下组合场景全部使用第三章实测过的命令形态,回显均为真实运行结果。
4.1 场景一:后台运行脚本,日志落盘、PID 落盘
长任务的标准三件套:显式日志(不用 nohup.out)、记录 PID(便于停止)、确认进程在跑。
$ nohup /root/demo/nohup/task.sh > /root/demo/nohup/run1.log 2>&1 & echo "PID=$!"
PID=114
$ cat /root/demo/nohup/run1.log
task running: line 1
task running: line 2
task running: line 3
task finished
要点:echo "PID=$!" 立刻拿到后台任务 PID;任务结束后 cat 日志确认全部输出完整落盘,无丢行。
4.2 场景二:记录 PID 并安全停止长任务
$ nohup sleep 300 >/dev/null 2>&1 & echo $! > /root/demo/nohup/pid.txt; sleep 0.3
$ cat /root/demo/nohup/pid.txt
120
$ ps -o pid,ppid,cmd -p 120
PID PPID CMD
120 88 sleep 300
$ kill $(cat /root/demo/nohup/pid.txt); echo rc=$?
rc=0
$ ps -o pid,cmd -p 120; echo rc=$?
PID CMD
rc=1
要点:PID 写入文件后,即使换会话/换人接手,kill $(cat pid.txt) 也能精确停止;ps 复核 rc=1 确认已退出,避免"杀了个寂寞"。
4.3 场景三:会话退出后任务仍在(重连查看输出)
结合 3.6 节实测:在即将退出的会话里启动 nohup 任务,会话结束后进程被 1 号进程收养、继续运行,日志已落盘。本环境用 bash -c 模拟"会话 shell 启动任务后退出":
$ bash -c 'nohup sleep 100 >/dev/null 2>&1 & echo $!'
18217
$ ps -o pid,ppid,cmd -p 18217
PID PPID CMD
18217 1 sleep 100
要点:PPID=1 说明启动它的会话 shell 已经退出,任务被 1 号进程收养仍在运行(PPID 列为 1 是"任务已脱离原会话"的标志)。日常 SSH 场景下,断线重连后直接读日志文件(如 4.1 场景的 cat /root/demo/nohup/run1.log)即可掌握进度,无需依赖终端保持连接;若当时显式重定向了日志,重连后 cat 对应文件即可。
4.4 场景四:显式重定向接管全部输出
不需要 nohup.out、想自定义日志路径时(3.3 节实测的组合):
$ nohup /root/demo/nohup/task.sh > /root/demo/nohup/task.log 2>&1 & sleep 0.3
$ ls -l /root/demo/nohup/
total 8
-rw-r--r-- 1 root root 77 Oct 15 10:11 task.log
-rwxr-xr-x 1 root root 106 Oct 15 10:11 task.sh
$ cat /root/demo/nohup/task.log
task running: line 1
task running: line 2
task running: line 3
task finished
要点:日志文件名、目录完全由你掌控,多任务可各自指定独立日志(如 task.log 与 4.1 的 run1.log),互不混写。
五、注意事项与常见问题
Q1:nohup 和 & 是什么关系?缺一个行不行?
& 只负责"放入后台",不改变信号处置——终端关闭/SSH 断开时 shell 会向作业发 SIGHUP,作业照样死;nohup 只负责"忽略 SIGHUP",不加 & 时任务仍占着终端前台跑。两者组合 nohup COMMAND & 才同时满足"后台 + 抗挂断"。
Q2:为什么我的 nohup 没有打印 appending output to 'nohup.out'?
提示语只在 stdout 是终端时出现。两种常见情况不会打印:一是已显式重定向(3.3 节);二是 stdout 本来就不是终端(如管道场景 3.4 节第三组,此时提示语变为 ignoring input and redirecting stderr to stdout 或完全没有)。这不是故障,参照 3.4 节对照表判断即可。
Q3:nohup.out 会无限变大吗?怎么治理?
stdout 是终端时 nohup 始终追加写 nohup.out,长任务确实会持续增长。治理首选 3.3 节的显式重定向:把日志引到专有路径,纳入常规日志轮转管理;nohup.out 只作为"忘写重定向时的兜底"。
Q4:kill 之后 ps 里还有 [sleep] <defunct>,是没杀干净吗?
不是。<defunct> 是僵尸态:进程实体已终止,只剩退出状态等待父进程 wait 回收。本环境实测(清理阶段)就出现了:
$ ps -o pid,cmd -p 18171,18208,18217,18220; echo rc=$?
PID CMD
18217 [sleep] <defunct>
18220 [sleep] <defunct>
rc=0
18171、18208(父 shell 尚在时被 kill)已被正常回收,而 18217、18220 的父 shell 先退出了,它们死后由收养者回收。僵尸不占 CPU/内存,但会占用 PID 表项;避免方法是让父进程及时 wait,或确认收养者具备回收能力。
Q5:任务已经跑起来了才发现没加 nohup,怎么办?
nohup 对已运行进程无效(3.8 节实测 rc=125)。两个补救方向:一是 bash 内置 disown %作业号,把作业移出作业表,shell 退出时不再向它发 SIGHUP;二是如果可以重启,用 setsid 或 nohup 重新拉起。长时间任务建议养成"启动即 nohup + 记录 PID + 显式日志"的习惯(4.1/4.2 场景)。
Q6:SIGHUP 到底什么时候真的会发?
典型场景:终端(含 SSH 会话)关闭时,终端驱动向前台进程组发送 HUP;交互式登录 shell 开启 huponexit(shopt -s huponexit)后退出时向全部作业发 HUP。而非交互 shell(如 bash -c、脚本)退出默认不发 HUP——这正是 3.7 节对照组也存活的原因。生产中 SSH 断线最常见,nohup 因此成为长任务标配。
六、参考链接
- Ubuntu 22.04 (jammy) nohup 手册页:https://manpages.ubuntu.com/manpages/jammy/en/man1/nohup.1.html
- GNU coreutils 官方手册 nohup 章节:https://www.gnu.org/software/coreutils/manual/html_node/nohup-invocation.html
- GNU coreutils 官方手册首页:https://www.gnu.org/software/coreutils/manual/
- signal(7) 信号说明(SIGHUP 语义):https://man7.org/linux/man-pages/man7/signal.7.html
- bash 参考手册(作业控制与 disown):https://www.gnu.org/software/bash/manual/html_node/Job-Control-Builtins.html