Linux nohup命令

nohup命令用于在终端关闭后继续运行程序,忽略挂起(SIGHUP)信号,使进程在后台持续运行。
注意:nohup通常与&(后台运行)和2>&1(重定向错误输出)一起使用。

一、命令简介

nohup命令(No Hang Up的缩写)用于运行不受挂起信号影响的命令。当用户注销(logout)或者网络连接断开时,终端会收到SIGHUP信号,所有从该终端启动的进程都会被终止。使用nohup可以防止这种情况发生,使命令继续在后台运行。

主要应用场景:

  • SSH远程登录后运行长时间任务
  • 需要在服务器后台持续运行的脚本
  • 避免网络断开导致任务终止
  • 创建简单的守护进程
  • 定时任务或批量处理

二、命令语法

nohup 命令 [参数]
nohup 命令 [参数] > 输出文件 2>&1 &

三、工作原理

nohup命令的工作原理:

  1. 忽略SIGHUP信号(挂起信号)
  2. 将标准输出(stdout)重定向到nohup.out文件(如果未指定输出文件)
  3. 将标准错误(stderr)重定向到标准输出
  4. 让命令在后台运行

四、使用示例

1. 基本用法

# 最简单的nohup用法
nohup python script.py &

# 查看nohup输出
cat nohup.out

# 查看nohup进程
ps aux | grep script.py

2. 指定输出文件

# 将输出重定向到指定文件
nohup java -jar app.jar > app.log &

# 追加输出到现有文件
nohup ./start_server.sh >> server.log 2>&1 &

# 将标准输出和错误输出分别重定向
nohup ./program > output.log 2> error.log &

3. 重定向所有输出

# 标准输出和错误输出都重定向到同一个文件
nohup ./myprogram > /dev/null 2>&1 &

# 丢弃所有输出(静默运行)
nohup ./background_task.sh > /dev/null 2>&1 &

# 只重定向错误输出,标准输出到终端
nohup ./script.sh 2> error.log &

4. 结合其他命令使用

# 使用nohup运行多个命令
nohup bash -c "command1 && command2" > output.log 2>&1 &

# 在nohup中使用管道
nohup cat access.log | grep "ERROR" > errors.log 2>&1 &

# 后台运行复杂的shell脚本
nohup sh -c '
    for i in {1..10}
    do
        echo "Processing item $i"
        ./process_item.sh $i
        sleep 1
    done
' > process.log 2>&1 &

5. 实际应用场景

# 后台启动Web服务器
nohup python -m http.server 8080 > webserver.log 2>&1 &

# 数据库备份任务
nohup mysqldump -u root -p database > backup.sql 2> backup_error.log &

# 长时间运行的压缩任务
nohup tar -czf archive.tar.gz /path/to/large/directory > compression.log 2>&1 &

# 监控日志文件
nohup tail -f /var/log/syslog | grep "ERROR" > errors_monitor.log 2>&1 &

6. 查看和管理nohup进程

# 查找所有nohup进程
ps aux | grep nohup
ps -ef | grep nohup

# 查找特定nohup进程
pgrep -f "python script.py"
ps aux | grep "java.*jar"

# 查看进程树
pstree -p | grep -A 5 -B 5 nohup

# 查看进程资源使用情况
top -p $(pgrep -f "python script.py")

# 查看进程打开的文件
lsof -p $(pgrep -f "java.*jar")

7. 停止nohup进程

# 找到进程ID并杀死
PID=$(pgrep -f "python script.py")
kill $PID

# 强制杀死进程
kill -9 $PID

# 杀死所有匹配的进程
pkill -f "java.*jar"

# 优雅停止(发送SIGTERM信号)
killall -TERM process_name

五、输出文件详解

输出文件 说明 默认位置
nohup.out 默认输出文件(当未指定重定向时) 当前目录
~/.nohup.out 如果当前目录不可写时的备用位置 用户家目录
自定义文件.log 用户指定的输出文件 指定路径

六、实用技巧

技巧1:创建nohup运行脚本
#!/bin/bash
# run_backup.sh - 使用nohup运行备份脚本

# 定义日志文件
LOG_FILE="/var/log/backup_$(date +%Y%m%d_%H%M%S).log"

echo "Starting backup at $(date)" | tee -a "$LOG_FILE"

# 使用nohup运行备份
nohup /usr/local/bin/backup.sh >> "$LOG_FILE" 2>&1 &

# 获取进程ID
BACKUP_PID=$!
echo "Backup started with PID: $BACKUP_PID" | tee -a "$LOG_FILE"
echo "Log file: $LOG_FILE" | tee -a "$LOG_FILE"

# 保存PID到文件以便后续管理
echo "$BACKUP_PID" > /var/run/backup.pid
技巧2:监控nohup进程状态
#!/bin/bash
# monitor_nohup.sh - 监控nohup进程状态

PID_FILE="/var/run/myapp.pid"

if [ ! -f "$PID_FILE" ]; then
    echo "PID file not found: $PID_FILE"
    exit 1
fi

PID=$(cat "$PID_FILE")

# 检查进程是否存在
if ps -p "$PID" > /dev/null 2>&1; then
    echo "Process $PID is running"

    # 查看进程状态
    ps -p "$PID" -o pid,ppid,user,%cpu,%mem,cmd

    # 查看进程打开的文件
    echo "=== Open files ==="
    lsof -p "$PID" | head -10

    # 查看进程资源使用
    echo "=== Resource usage ==="
    top -b -n 1 -p "$PID" | tail -2
else
    echo "Process $PID is not running"

    # 检查是否有僵尸进程
    if ps -p "$PID" -o state | grep -q Z; then
        echo "Warning: Process $PID is a zombie!"
    fi
fi
技巧3:批量启动nohup任务
#!/bin/bash
# start_workers.sh - 批量启动worker进程

WORKER_COUNT=5
LOG_DIR="/var/log/workers"
mkdir -p "$LOG_DIR"

echo "Starting $WORKER_COUNT workers..."

for i in $(seq 1 $WORKER_COUNT); do
    LOG_FILE="$LOG_DIR/worker_$i.log"
    echo "Starting worker $i (log: $LOG_FILE)"

    nohup /usr/local/bin/worker.py --id "$i" > "$LOG_FILE" 2>&1 &

    WORKER_PID=$!
    echo "Worker $i started with PID: $WORKER_PID"

    # 保存PID到数组
    PIDS[$i]=$WORKER_PID
done

echo "All workers started. PIDs: ${PIDS[*]}"
技巧4:自动重启nohup进程
#!/bin/bash
# auto_restart.sh - 自动重启失败的nohup进程

APP_CMD="/usr/local/bin/myapp.py"
LOG_FILE="/var/log/myapp.log"
PID_FILE="/var/run/myapp.pid"
MAX_RETRIES=3
RETRY_COUNT=0

while [ $RETRY_COUNT -lt $MAX_RETRIES ]; do
    echo "Starting application (attempt $((RETRY_COUNT+1))) at $(date)"

    # 使用nohup启动应用
    nohup $APP_CMD >> "$LOG_FILE" 2>&1 &
    APP_PID=$!

    echo "$APP_PID" > "$PID_FILE"
    echo "Application started with PID: $APP_PID"

    # 等待进程结束
    wait $APP_PID
    EXIT_CODE=$?

    echo "Application exited with code: $EXIT_CODE at $(date)"

    # 检查是否需要重启
    if [ $EXIT_CODE -eq 0 ] || [ $EXIT_CODE -eq 130 ]; then
        echo "Normal exit, not restarting"
        break
    else
        RETRY_COUNT=$((RETRY_COUNT+1))
        echo "Abnormal exit, restarting in 5 seconds... (retry $RETRY_COUNT/$MAX_RETRIES)"
        sleep 5
    fi
done

if [ $RETRY_COUNT -eq $MAX_RETRIES ]; then
    echo "Max retries reached. Giving up."
    exit 1
fi

七、nohup vs screen vs tmux

工具 优点 缺点 适用场景
nohup 简单易用,无需额外安装,系统自带 无法重新连接查看输出,管理不便 简单的后台任务,无需交互的任务
screen 可以重新连接会话,多个窗口,会话共享 配置较复杂,需要额外学习 长时间运行需要交互的任务
tmux 功能强大,窗口分割,会话共享,高度可配置 学习曲线较陡,配置复杂 复杂的多任务管理,开发环境
systemd 系统级管理,自动重启,日志管理,资源控制 配置复杂,需要root权限 生产环境服务,需要系统级管理的应用

八、与systemd服务对比

# nohup方式(简单,适合临时任务)
nohup /opt/myapp/bin/start.sh > /var/log/myapp.log 2>&1 &

# systemd方式(生产环境推荐)
sudo systemctl start myapp.service
sudo systemctl enable myapp.service  # 开机自启

# systemd服务文件示例 /etc/systemd/system/myapp.service
[Unit]
Description=My Application
After=network.target

[Service]
Type=simple
User=appuser
WorkingDirectory=/opt/myapp
ExecStart=/opt/myapp/bin/start.sh
Restart=always
RestartSec=10
StandardOutput=syslog
StandardError=syslog
SyslogIdentifier=myapp

[Install]
WantedBy=multi-user.target

九、注意事项

  • 输出文件位置:nohup默认在当前目录创建nohup.out,如果当前目录不可写,会创建在~/nohup.out
  • 磁盘空间:长时间运行的nohup进程可能产生大量日志,注意磁盘空间监控
  • 进程管理:需要自己管理进程ID和日志文件,没有自动重启机制
  • 信号处理:nohup只忽略SIGHUP信号,其他信号(如SIGTERM、SIGKILL)仍然有效
  • 权限问题:nohup以当前用户权限运行,注意文件权限和访问限制
  • 环境变量:nohup会继承当前shell的环境变量,注意PATH和其他环境设置
  • 生产环境:对于重要服务,建议使用systemd或supervisor等专业进程管理工具

十、常见问题

&(后台运行):只是将命令放到后台执行,终端关闭时进程仍然会收到SIGHUP信号而终止。

nohup:忽略SIGHUP信号,即使终端关闭,进程也会继续运行。

通常两者结合使用:nohup command &

# 仅后台运行,终端关闭会终止
./script.sh &

# nohup后台运行,终端关闭继续运行
nohup ./script.sh &

解决方法:

# 1. 使用自定义日志文件并定期清理
nohup ./program > /var/log/program.log 2>&1 &

# 2. 使用logrotate管理日志
# 创建logrotate配置 /etc/logrotate.d/myprogram
/var/log/program.log {
    daily
    rotate 7
    compress
    delaycompress
    missingok
    notifempty
    create 644 root root
}

# 3. 丢弃不需要的输出
nohup ./program > /dev/null 2>&1 &

# 4. 只保留错误日志
nohup ./program > /dev/null 2> error.log &

根据不同的重定向方式:

# 1. 查看默认输出文件
tail -f nohup.out
cat nohup.out

# 2. 查看自定义日志文件
tail -f /var/log/program.log

# 3. 实时查看输出(如果输出到文件)
tail -f /var/log/program.log

# 4. 查看最后N行
tail -100 /var/log/program.log

# 5. 搜索特定内容
grep "ERROR" /var/log/program.log

注意:如果输出被重定向到/dev/null,则无法查看输出内容。

nohup本身不支持开机自启,需要其他方法:

方法1:使用rc.local(简单但不推荐)

# 编辑/etc/rc.local
#!/bin/bash
nohup /path/to/your/command > /var/log/yourcommand.log 2>&1 &
exit 0

方法2:使用crontab(推荐)

# 编辑crontab -e
@reboot nohup /path/to/your/command > /var/log/yourcommand.log 2>&1 &

方法3:使用systemd服务(生产环境推荐)

# 创建systemd服务文件
sudo nano /etc/systemd/system/yourcommand.service

# 启用并启动服务
sudo systemctl enable yourcommand.service
sudo systemctl start yourcommand.service

方法4:使用init.d脚本(传统方法)

# 创建/etc/init.d/yourcommand脚本
sudo update-rc.d yourcommand defaults

十一、最佳实践

nohup使用最佳实践
  1. 始终指定输出文件:避免使用默认的nohup.out,明确指定日志文件路径
  2. 分离标准输出和错误输出:便于问题排查
  3. 保存进程ID:将PID保存到文件,便于后续管理
  4. 使用日志轮转:避免日志文件过大
  5. 监控磁盘空间:定期检查日志文件大小
  6. 生产环境使用systemd:对于重要服务,使用专业的进程管理工具
  7. 编写启动脚本:将复杂的nohup命令封装到脚本中
  8. 添加错误处理:在脚本中添加错误检查和重试机制
  9. 记录启动信息:在日志中记录启动时间、参数等信息
  10. 定期清理:清理旧的日志文件和僵尸进程