linux disown命令

disown命令 用于将后台作业从当前shell的作业表中移除,使其成为"孤儿进程",从而不受shell退出的影响。即使关闭终端或退出shell,这些进程也会继续运行。disown是Bash shell的内置命令。

命令格式

disown [选项] [作业号...]

常用选项

选项 说明
-h 标记作业,使其在shell退出时不接收SIGHUP信号
-a 处理所有作业(如果没有指定作业号)
-r 只处理运行中的作业

作业号表示方法

表示方法 说明 示例
%n 作业号n %1, %2
%str 以str开头的作业 %ping
%?str 包含str的作业 %?nginx
%%%+ 当前作业(最近的后台作业) %%
%- 前一个作业 %-

使用实例

1. 启动后台作业并分离(基本用法)
# 启动长时间运行的脚本
./long_task.sh &

# 查看作业号
jobs

# 分离作业(默认分离当前作业)
disown

# 或直接指定作业号
disown %1
2. 标记作业不受SIGHUP信号影响
# 启动后台作业
./server.sh &

# 标记作业但不从作业表中移除
disown -h %1

# 现在即使shell退出,该作业也不会收到SIGHUP信号
# 但仍然可以使用jobs命令看到该作业
3. 分离所有后台作业
# 启动多个后台作业
./task1.sh &
./task2.sh &
./task3.sh &

# 分离所有作业
disown -a
4. 只分离运行中的作业
# 分离所有运行中的作业
disown -r

# 这不会影响已停止的作业
5. 分离特定作业(按作业号)
# 启动多个作业
./job1.sh &  # 作业1
./job2.sh &  # 作业2
./job3.sh &  # 作业3

# 查看作业列表
jobs
# [1]   Running    ./job1.sh &
# [2]   Running    ./job2.sh &
# [3]   Running    ./job3.sh &

# 分离作业1和作业3
disown %1 %3
6. 分离包含特定字符串的作业
# 启动多个作业
ping google.com > ping.log &
tail -f /var/log/syslog > tail.log &

# 分离包含"ping"的作业
disown %?ping
7. 在脚本中使用disown
#!/bin/bash
# 启动守护进程并分离

echo "启动服务守护进程..."

# 启动服务
./daemon.sh &

# 获取作业PID
DAEMON_PID=$!

# 等待服务启动
sleep 2

# 分离作业
disown %%

echo "服务已启动并分离,PID: $DAEMON_PID"
echo "现在可以安全退出shell"
8. 与nohup配合使用
# 使用nohup启动作业
nohup ./long_task.sh > task.log 2>&1 &

# 然后使用disown分离
disown %%

# 这样即使终端关闭,任务也会继续运行
9. 分离已停止的作业
# 启动作业并停止
./task.sh &
# 按Ctrl+Z停止作业

# 查看作业状态
jobs
# [1]+  Stopped    ./task.sh

# 将作业放入后台
bg %1

# 然后分离
disown %1

实际输出示例

示例1:基本分离过程
$ sleep 3600 &
[1] 12345

$ jobs
[1]+  Running    sleep 3600 &

$ disown %1

$ jobs
# 没有输出,作业已从作业表中移除

# 但进程仍在运行
$ ps aux | grep sleep
user     12345  0.0  0.0   1234   567 pts/0    S    10:30   0:00 sleep 3600
示例2:使用-h选项
$ ./server.sh &
[1] 23456

$ disown -h %1

$ jobs
[1]+  Running    ./server.sh &

# 作业仍在作业表中,但已标记为不接收SIGHUP
# 可以继续使用fg/bg管理
示例3:分离多个作业
$ ./task1.sh &
[1] 12345
$ ./task2.sh &
[2] 23456
$ ./task3.sh &
[3] 34567

$ jobs
[1]   Running    ./task1.sh &
[2]-  Running    ./task2.sh &
[3]+  Running    ./task3.sh &

$ disown %1 %3

$ jobs
[2]-  Running    ./task2.sh &
# 只有作业2还在作业表中

进程、作业和会话的关系

重要概念理解:
  1. 进程:正在执行的程序实例
  2. 作业:Shell管理的进程或进程组
  3. 会话:一个或多个进程组的集合
  4. SIGHUP信号:当终端断开时发送给会话首进程的信号

disown的作用:将作业从shell的作业表中移除,使其不受shell会话的SIGHUP信号影响。

与相关命令对比

命令 作用 特点 适用场景
disown 将作业从作业表移除 Bash内置命令,只影响作业管理 已启动的作业需要长期运行
nohup 启动免疫SIGHUP的进程 外部命令,启动时即免疫SIGHUP 启动新任务时使用
setsid 在新会话中运行程序 完全独立的会话,与终端无关 需要完全脱离终端的进程
screen/tmux 终端多路复用器 提供持久的终端会话 交互式任务的长期运行
bg/fg 作业控制命令 在后台/前台运行作业 作业的临时管理

实用场景

服务部署
  • 启动后台服务进程
  • 部署长时间运行的任务
  • 守护进程管理
  • 批处理作业
远程工作
  • SSH会话中的长时间任务
  • 文件传输保持
  • 远程编译任务
  • 数据备份任务
脚本自动化
  • 自动化脚本中的后台任务
  • 定时任务管理
  • 监控脚本部署
  • 任务调度系统
系统维护
  • 系统升级过程
  • 日志轮转任务
  • 数据库维护
  • 清理任务

实用命令组合

# 安全的启动和分离模式
(./long_running_script.sh > script.log 2>&1 &) && disown

# 监控并分离多个任务
for task in task1 task2 task3; do
    ./${task}.sh &
    disown %%
done

# 在函数中启动并分离
start_daemon() {
    local daemon=$1
    "./${daemon}.sh" &
    local pid=$!
    disown %%
    echo "启动 ${daemon}, PID: ${pid}"
}

# 使用子shell避免影响当前作业表
(./critical_task.sh & disown)

# 结合trap处理信号
trap '' HUP  # 忽略SIGHUP信号
./important.sh &
disown %%

# 自动分离所有作业(在脚本结束时)
cleanup() {
    echo "分离所有后台作业..."
    disown -a
}
trap cleanup EXIT
注意事项:
  • disown是Bash内置命令,在其他shell中可能不可用
  • 使用disown后,作业将无法使用fgbg等作业控制命令管理
  • 分离的作业仍然会输出到当前终端,除非重定向输出
  • 如果作业有未重定向的输出,终端关闭时可能会挂起
  • disown -h只是标记作业,作业仍在作业表中
  • 分离的作业无法通过作业号引用,只能通过PID管理
  • 在某些系统中,分离的作业可能仍然会收到其他信号

常见问题解答

主要区别:

  • nohup:在启动命令时即免疫SIGHUP,自动重定向输出到nohup.out
  • disown:对已启动的作业进行操作,不自动重定向输出

使用建议:

# 启动新任务时使用nohup
nohup ./task.sh > task.log 2>&1 &

# 对已启动的任务使用disown
./task.sh &
disown %1

disown后只能通过PID管理进程:

# 启动时记录PID
./daemon.sh &
DAEMON_PID=$!
disown %%

# 通过PID管理
kill $DAEMON_PID               # 发送默认信号
kill -TERM $DAEMON_PID         # 优雅终止
kill -9 $DAEMON_PID            # 强制终止

# 查看进程状态
ps -p $DAEMON_PID

# 查看进程树
pstree -p $DAEMON_PID

# 查看进程打开的文件
lsof -p $DAEMON_PID

可以设置信号处理或使用wait命令:

# 方法1:忽略SIGCHLD信号(不推荐,可能影响其他子进程)
trap "" CHLD

# 方法2:在脚本中处理子进程
#!/bin/bash
# parent.sh
cleanup() {
    wait  # 等待所有子进程
    echo "所有子进程已结束"
}
trap cleanup EXIT

./child.sh &
disown %%

# 方法3:使用双fork技术
(./daemon.sh &) &  # 子进程再fork孙进程
disown %%

实际脚本示例

示例1:安全的服务启动脚本
#!/bin/bash
# service_manager.sh - 服务启动和管理脚本

SERVICE_NAME="myapp"
SERVICE_SCRIPT="./myapp.sh"
LOG_FILE="/var/log/myapp.log"
PID_FILE="/var/run/myapp.pid"

# 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
NC='\033[0m' # No Color

start_service() {
    echo -n "启动 $SERVICE_NAME..."

    # 检查是否已在运行
    if [ -f "$PID_FILE" ]; then
        local pid=$(cat "$PID_FILE")
        if ps -p "$pid" > /dev/null 2>&1; then
            echo -e "${RED}失败:服务已在运行(PID: $pid)${NC}"
            return 1
        fi
    fi

    # 启动服务
    "$SERVICE_SCRIPT" > "$LOG_FILE" 2>&1 &
    local service_pid=$!

    # 保存PID
    echo "$service_pid" > "$PID_FILE"

    # 分离服务进程
    disown %%

    echo -e "${GREEN}成功${NC}"
    echo "PID: $service_pid"
    echo "日志: $LOG_FILE"

    # 验证服务是否启动成功
    sleep 2
    if ps -p "$service_pid" > /dev/null 2>&1; then
        return 0
    else
        echo -e "${RED}警告:服务进程可能已退出${NC}"
        return 1
    fi
}

stop_service() {
    echo -n "停止 $SERVICE_NAME..."

    if [ ! -f "$PID_FILE" ]; then
        echo -e "${RED}失败:PID文件不存在${NC}"
        return 1
    fi

    local pid=$(cat "$PID_FILE")

    if ps -p "$pid" > /dev/null 2>&1; then
        # 优雅停止
        kill -TERM "$pid"

        # 等待进程结束
        for i in {1..30}; do
            if ! ps -p "$pid" > /dev/null 2>&1; then
                break
            fi
            sleep 1
        done

        # 强制停止(如果需要)
        if ps -p "$pid" > /dev/null 2>&1; then
            kill -9 "$pid"
            sleep 1
        fi

        rm -f "$PID_FILE"
        echo -e "${GREEN}成功${NC}"
        return 0
    else
        echo -e "${RED}失败:进程不存在${NC}"
        rm -f "$PID_FILE"
        return 1
    fi
}

case "$1" in
    start)
        start_service
        ;;
    stop)
        stop_service
        ;;
    restart)
        stop_service
        sleep 2
        start_service
        ;;
    status)
        if [ -f "$PID_FILE" ]; then
            local pid=$(cat "$PID_FILE")
            if ps -p "$pid" > /dev/null 2>&1; then
                echo "$SERVICE_NAME 正在运行 (PID: $pid)"
            else
                echo "$SERVICE_NAME 已停止"
            fi
        else
            echo "$SERVICE_NAME 已停止"
        fi
        ;;
    *)
        echo "用法: $0 {start|stop|restart|status}"
        exit 1
        ;;
esac
示例2:批量任务处理器
#!/bin/bash
# batch_processor.sh - 批量处理任务并分离

TASK_DIR="./tasks"
LOG_DIR="./logs"
MAX_CONCURRENT=5  # 最大并发数

# 创建日志目录
mkdir -p "$LOG_DIR"

# 获取所有任务文件
TASKS=("$TASK_DIR"/*.sh)

echo "开始处理 ${#TASKS[@]} 个任务..."
echo "最大并发数: $MAX_CONCURRENT"

# 任务计数器
declare -A TASK_PIDS
COMPLETED=0
FAILED=0

# 处理任务函数
process_task() {
    local task_file="$1"
    local task_name=$(basename "$task_file" .sh)
    local log_file="$LOG_DIR/${task_name}_$(date +%Y%m%d_%H%M%S).log"

    echo "启动任务: $task_name"

    # 执行任务并记录PID
    "$task_file" > "$log_file" 2>&1 &
    local task_pid=$!

    # 分离任务
    disown %%

    # 记录PID
    TASK_PIDS["$task_pid"]="$task_name"

    echo "任务 $task_name 已启动 (PID: $task_pid, 日志: $log_file)"
}

# 主处理循环
for task_file in "${TASKS[@]}"; do
    # 检查并发数
    while [ ${#TASK_PIDS[@]} -ge $MAX_CONCURRENT ]; do
        # 检查是否有任务完成
        for pid in "${!TASK_PIDS[@]}"; do
            if ! kill -0 "$pid" 2>/dev/null; then
                # 任务完成
                local task_name="${TASK_PIDS[$pid]}"
                if wait "$pid" 2>/dev/null; then
                    echo "任务完成: $task_name"
                    ((COMPLETED++))
                else
                    echo "任务失败: $task_name"
                    ((FAILED++))
                fi
                unset TASK_PIDS["$pid"]
            fi
        done
        sleep 1
    done

    # 启动新任务
    process_task "$task_file"
done

# 等待剩余任务
echo "等待剩余任务完成..."
for pid in "${!TASK_PIDS[@]}"; do
    if wait "$pid" 2>/dev/null; then
        echo "任务完成: ${TASK_PIDS[$pid]}"
        ((COMPLETED++))
    else
        echo "任务失败: ${TASK_PIDS[$pid]}"
        ((FAILED++))
    fi
done

# 输出统计
echo ""
echo "=== 任务处理完成 ==="
echo "总任务数: ${#TASKS[@]}"
echo "成功: $COMPLETED"
echo "失败: $FAILED"
echo "所有任务已分离,可在后台继续运行"

相关命令

nohup

启动免疫SIGHUP的进程

查看详情
screen/tmux

终端多路复用器

查看详情
jobs

显示作业列表

查看详情