disown [选项] [作业号...]
| 选项 | 说明 |
|---|---|
-h |
标记作业,使其在shell退出时不接收SIGHUP信号 |
-a |
处理所有作业(如果没有指定作业号) |
-r |
只处理运行中的作业 |
| 表示方法 | 说明 | 示例 |
|---|---|---|
%n |
作业号n | %1, %2 |
%str |
以str开头的作业 | %ping |
%?str |
包含str的作业 | %?nginx |
%% 或 %+ |
当前作业(最近的后台作业) | %% |
%- |
前一个作业 | %- |
# 启动长时间运行的脚本
./long_task.sh &
# 查看作业号
jobs
# 分离作业(默认分离当前作业)
disown
# 或直接指定作业号
disown %1
# 启动后台作业
./server.sh &
# 标记作业但不从作业表中移除
disown -h %1
# 现在即使shell退出,该作业也不会收到SIGHUP信号
# 但仍然可以使用jobs命令看到该作业
# 启动多个后台作业
./task1.sh &
./task2.sh &
./task3.sh &
# 分离所有作业
disown -a
# 分离所有运行中的作业
disown -r
# 这不会影响已停止的作业
# 启动多个作业
./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
# 启动多个作业
ping google.com > ping.log &
tail -f /var/log/syslog > tail.log &
# 分离包含"ping"的作业
disown %?ping
#!/bin/bash
# 启动守护进程并分离
echo "启动服务守护进程..."
# 启动服务
./daemon.sh &
# 获取作业PID
DAEMON_PID=$!
# 等待服务启动
sleep 2
# 分离作业
disown %%
echo "服务已启动并分离,PID: $DAEMON_PID"
echo "现在可以安全退出shell"
# 使用nohup启动作业
nohup ./long_task.sh > task.log 2>&1 &
# 然后使用disown分离
disown %%
# 这样即使终端关闭,任务也会继续运行
# 启动作业并停止
./task.sh &
# 按Ctrl+Z停止作业
# 查看作业状态
jobs
# [1]+ Stopped ./task.sh
# 将作业放入后台
bg %1
# 然后分离
disown %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
$ ./server.sh &
[1] 23456
$ disown -h %1
$ jobs
[1]+ Running ./server.sh &
# 作业仍在作业表中,但已标记为不接收SIGHUP
# 可以继续使用fg/bg管理
$ ./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还在作业表中
disown的作用:将作业从shell的作业表中移除,使其不受shell会话的SIGHUP信号影响。
| 命令 | 作用 | 特点 | 适用场景 |
|---|---|---|---|
disown |
将作业从作业表移除 | Bash内置命令,只影响作业管理 | 已启动的作业需要长期运行 |
nohup |
启动免疫SIGHUP的进程 | 外部命令,启动时即免疫SIGHUP | 启动新任务时使用 |
setsid |
在新会话中运行程序 | 完全独立的会话,与终端无关 | 需要完全脱离终端的进程 |
screen/tmux |
终端多路复用器 | 提供持久的终端会话 | 交互式任务的长期运行 |
bg/fg |
作业控制命令 | 在后台/前台运行作业 | 作业的临时管理 |
# 安全的启动和分离模式
(./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后,作业将无法使用fg、bg等作业控制命令管理disown -h只是标记作业,作业仍在作业表中主要区别:
nohup:在启动命令时即免疫SIGHUP,自动重定向输出到nohup.outdisown:对已启动的作业进行操作,不自动重定向输出使用建议:
# 启动新任务时使用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 %%
#!/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
#!/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 "所有任务已分离,可在后台继续运行"