compose_down_all
交互式批量停止 Docker Compose 项目的管理脚本。
一键脚本
bash <(curl -sL gitee.com/meimolihan/linux-command_sh/raw/master/compose_down_all.sh) /vol1/1000/compose
效果预览
脚本源码
- 传参:
./脚本名.sh 项目根目录 - 不传参:
./脚本名.sh交互式
#!/bin/bash
set -uo pipefail
list_color_init() {
export gl_hui=$'\033[38;5;59m'
export gl_hong=$'\033[38;5;9m'
export gl_lv=$'\033[38;5;10m'
export gl_huang=$'\033[38;5;11m'
export gl_lan=$'\033[38;5;32m'
export gl_bai=$'\033[38;5;15m'
export gl_zi=$'\033[38;5;13m'
export gl_bufan=$'\033[38;5;14m'
export reset=$'\033[0m'
}
list_color_init
log_info() { echo -e "${gl_lan}[信息]${gl_bai} $*"; }
log_ok() { echo -e "${gl_lv}[成功]${gl_bai} $*"; }
log_warn() { echo -e "${gl_huang}[警告]${gl_bai} $*"; }
log_error() { echo -e "${gl_hong}[错误]${gl_bai} $*" >&2; }
sleep_fractional() {
local seconds=$1
if sleep "$seconds" 2>/dev/null; then
return 0
fi
if command -v perl >/dev/null 2>&1; then
perl -e "select(undef, undef, undef, $seconds)"
return 0
fi
if command -v python3 >/dev/null 2>&1; then
python3 -c "import time; time.sleep($seconds)"
return 0
elif command -v python >/dev/null 2>&1; then
python -c "import time; time.sleep($seconds)"
return 0
fi
local int_seconds=$(echo "$seconds" | awk '{print int($1+0.999)}')
sleep "$int_seconds"
}
exit_animation() {
echo -ne "${gl_lv}即将退出 ${gl_hong}.${gl_huang}.${gl_lv}.${gl_bai}\c"
sleep_fractional 0.5
echo -ne "${gl_hong}.${gl_huang}.${gl_lv}.${gl_bai}\c"
sleep_fractional 0.6
echo ""
clear
}
exit_script() {
echo ""
echo -ne "${gl_hong}感谢使用,再见!${gl_hong}.${gl_huang}.${gl_lv}.${gl_bai}\c"
sleep_fractional 0.5
echo -ne "${gl_hong}.${gl_huang}.${gl_lv}.${gl_bai}\c"
sleep_fractional 0.6
clear
exit 0
}
cancel_return() {
local menu_name="${1:-上一级选单}"
echo -e "${gl_lv}即将返回到 ${gl_huang}${menu_name}${gl_lv}${gl_hong}.${gl_huang}.${gl_lv}.${gl_bai} \c"
sleep_fractional 0.6
echo ""
clear
}
stop_all_compose_projects() {
local COMPOSE_WORK_DIR="$1"
local SKIP_CONFIRM="${2:-false}"
clear
echo -e ""
echo -e "${gl_zi}>>> 停止所有 Compose 项目${gl_bai}"
echo -e "${gl_bufan}————————————————————————————————————————————————${gl_bai}"
if ! docker info &>/dev/null; then
log_error "Docker 服务未运行"
exit_animation
return 1
fi
if [[ ! -d "$COMPOSE_WORK_DIR" ]]; then
log_error "目录不存在: $COMPOSE_WORK_DIR"
exit_animation
return 1
fi
echo -e "${gl_bai}工作目录: ${gl_huang}$COMPOSE_WORK_DIR${gl_bai}"
echo -e "${gl_bufan}————————————————————————————————————————————————${gl_bai}"
if [[ "$SKIP_CONFIRM" != "true" ]]; then
read -r -e -p "$(echo -e "${gl_hong}警告: ${gl_bai}确定停止 ${gl_huang}$COMPOSE_WORK_DIR${gl_bai} 下的所有 Compose 项目吗?(${gl_lv}y${gl_bai}/${gl_hong}N${gl_bai}): ")" confirm
if [[ ! "$confirm" =~ ^[Yy]$ ]]; then
echo -e "${gl_huang}已取消${gl_bai}"
exit_animation
return
fi
fi
if ! cd "$COMPOSE_WORK_DIR" 2>/dev/null; then
log_error "无法进入目录: $COMPOSE_WORK_DIR"
exit_animation
return
fi
local stopped_count=0
local total_count=0
echo -e "${gl_bai}正在扫描 ${gl_huang}$COMPOSE_WORK_DIR${gl_bai} 下的 Compose 项目${gl_hong}.${gl_huang}.${gl_lv}.${gl_bai}"
echo -e "${gl_bufan}————————————————————————————————————————————————${gl_bai}"
if [[ -f "docker-compose.yml" ]] || [[ -f "docker-compose.yaml" ]]; then
local current_dir_name=$(basename "$(pwd)")
echo -e ""
echo -e "${gl_huang}>>> 停止项目: ${gl_lv}$current_dir_name${gl_bai} (当前目录)"
if command -v docker-compose &>/dev/null; then
docker-compose down
elif docker compose version &>/dev/null; then
docker compose down
else
log_error "未找到 docker-compose 命令"
return
fi
if [[ $? -eq 0 ]]; then
echo -e "${gl_lv}✓ 已停止: $current_dir_name${gl_bai}"
stopped_count=$((stopped_count + 1))
fi
total_count=$((total_count + 1))
fi
for dir in */; do
if [[ -d "$dir" ]]; then
cd "$dir" 2>/dev/null || continue
if [[ -f "docker-compose.yml" ]] || [[ -f "docker-compose.yaml" ]]; then
local project_name=$(basename "$dir")
echo -e ""
echo -e "${gl_huang}>>> 停止项目: ${gl_lv}$project_name${gl_bai}"
if command -v docker-compose &>/dev/null; then
docker-compose down
elif docker compose version &>/dev/null; then
docker compose down
else
log_error "未找到 docker-compose 命令"
cd ..
continue
fi
if [[ $? -eq 0 ]]; then
echo -e "${gl_lv}✓ 已停止: $project_name${gl_bai}"
stopped_count=$((stopped_count + 1))
fi
total_count=$((total_count + 1))
fi
cd .. 2>/dev/null
fi
done
echo -e "${gl_bufan}————————————————————————————————————————————————${gl_bai}"
if [[ $total_count -eq 0 ]]; then
echo -e "${gl_huang}在 ${gl_hong}$COMPOSE_WORK_DIR${gl_huang} 中没有找到 Compose 项目${gl_bai}"
else
echo -e "${gl_bai}停止完成${gl_bai}"
echo -e "${gl_bai}总计项目: ${gl_huang}$total_count${gl_bai}"
echo -e "${gl_bai}成功停止: ${gl_lv}$stopped_count${gl_bai}"
fi
echo -e "${gl_bufan}————————————————————————————————————————————————${gl_bai}"
if [[ "$SKIP_CONFIRM" == "true" ]]; then
return
fi
echo -e "${gl_bai}按任意键继续${gl_hong}.${gl_huang}.${gl_lv}.${gl_bai} \c"
read -r -n 1 -s -r -p ""
echo ""
clear
}
interactive_stop() {
clear
echo -e ""
echo -e "${gl_zi}>>> 停止所有 Compose 项目${gl_bai}"
echo -e "${gl_bufan}————————————————————————————————————————————————${gl_bai}"
if ! docker info &>/dev/null; then
log_error "Docker 服务未运行"
exit_animation
return 1
fi
local preset_dirs=(
"/compose"
"/mnt/compose"
"/vol1/1000/compose"
"/vol2/1000/compose"
)
log_info "正在扫描运行中的 docker-compose 项目${gl_hong}.${gl_huang}.${gl_lv}.${gl_bai}"
local running_projects=()
if command -v docker &>/dev/null && docker info &>/dev/null; then
while IFS= read -r dir; do
[[ -z "$dir" ]] && continue
[[ ! -d "$dir" ]] && continue
running_projects+=("$(realpath "$dir")")
done < <(
docker ps --format '{{.Names}}' 2>/dev/null |
xargs -I{} sh -c 'docker inspect {} 2>/dev/null | jq -r ".[0].Config.Labels[\"com.docker.compose.project.working_dir\"]" 2>/dev/null || echo ""' |
sort -u
)
fi
local recommended_dirs=()
for preset_dir in "${preset_dirs[@]}"; do
for running_dir in "${running_projects[@]}"; do
if [[ "$running_dir" == "$preset_dir" ]] || [[ "$running_dir" == "$preset_dir"* ]]; then
recommended_dirs+=("$preset_dir")
break
fi
done
done
recommended_dirs=($(printf "%s\n" "${recommended_dirs[@]}" | sort -u))
if [[ ${#running_projects[@]} -gt 0 ]]; then
echo -e "${gl_bai}扫描到运行中的 docker-compose 项目目录:${gl_bai}"
for dir in "${running_projects[@]}"; do
echo -e " ${gl_lv}•${gl_bai} ${dir}"
done
if [[ ${#recommended_dirs[@]} -gt 0 ]]; then
echo -e "${gl_bai}推荐的工作目录:${gl_bai}"
for dir in "${recommended_dirs[@]}"; do
echo -e " ${gl_hong}★${gl_bai} ${dir}"
done
fi
else
echo -e "${gl_huang}未找到运行中的 docker-compose 项目${gl_bai}"
fi
echo -e "${gl_bufan}————————————————————————————————————————————————${gl_bai}"
echo -e ""
echo -e "${gl_huang}>>> 请选择要停止的工作目录:${gl_bai}"
echo -e "${gl_bufan}————————————————————————————————————————————————${gl_bai}"
for i in "${!preset_dirs[@]}"; do
local marker=""
local dir_status=""
for rd in "${recommended_dirs[@]}"; do
if [[ "${preset_dirs[i]}" == "$rd" ]]; then
marker=" ${gl_hong}(推荐)${gl_bai}"
break
fi
done
if [[ -d "${preset_dirs[i]}" ]]; then
local compose_count=$(find "${preset_dirs[i]}" -maxdepth 2 -type f \( -name "docker-compose.yml" -o -name "docker-compose.yaml" \) 2>/dev/null | wc -l)
if [[ $compose_count -gt 0 ]]; then
dir_status="${gl_lv}[有 ${compose_count} 个项目]${gl_bai}"
else
dir_status="${gl_huang}[无 compose 项目]${gl_bai}"
fi
else
dir_status="${gl_huang}[目录不存在]${gl_bai}"
fi
echo -e "${gl_bufan}$((i + 1)).${gl_bai} ${preset_dirs[i]} $dir_status$marker"
done
echo -e "${gl_bufan}$((${#preset_dirs[@]} + 1)).${gl_bai} 手动指定路径"
echo -e "${gl_bufan}————————————————————————————————————————————————${gl_bai}"
echo -e "${gl_huang}0.${gl_bai} 返回上一级选单"
echo -e "${gl_bufan}————————————————————————————————————————————————${gl_bai}"
read -r -e -p "$(echo -e "${gl_bai}请输入你的选择 (${gl_huang}0${gl_bai}-${gl_hong}$((${#preset_dirs[@]} + 1))${gl_bai}): ")" dir_choice
local COMPOSE_WORK_DIR=""
if [[ -z "$dir_choice" ]] && [[ ${#recommended_dirs[@]} -gt 0 ]]; then
COMPOSE_WORK_DIR="${recommended_dirs[0]}"
echo -e "${gl_bai}使用推荐目录: ${gl_huang}$COMPOSE_WORK_DIR${gl_bai}"
elif [[ "$dir_choice" =~ ^[0-9]+$ ]]; then
if [[ $dir_choice -eq 0 ]]; then
cancel_return "Compose 项目管理"
return
elif [[ $dir_choice -le ${#preset_dirs[@]} ]]; then
COMPOSE_WORK_DIR="${preset_dirs[$((dir_choice - 1))]}"
elif [[ $dir_choice -eq $((${#preset_dirs[@]} + 1)) ]]; then
read -r -e -p "$(echo -e "${gl_bai}请输入自定义路径: ")" COMPOSE_WORK_DIR
else
log_error "无效选择"
exit_animation
return
fi
else
COMPOSE_WORK_DIR="$dir_choice"
fi
if [[ -z "$COMPOSE_WORK_DIR" ]]; then
log_warn "路径不能为空"
exit_animation
return
fi
if [[ ! -d "$COMPOSE_WORK_DIR" ]]; then
log_warn "目录不存在: $COMPOSE_WORK_DIR"
exit_animation
return
fi
stop_all_compose_projects "$COMPOSE_WORK_DIR" "false"
}
show_help() {
echo -e "${gl_lv}使用说明:${gl_bai}"
echo -e " ${gl_bai}$0 ${gl_huang}[选项] ${gl_lan}[目录]${gl_bai}"
echo -e ""
echo -e "${gl_lv}选项:${gl_bai}"
echo -e " ${gl_huang}-y, --yes${gl_bai} 自动确认,跳过提示"
echo -e " ${gl_huang}-h, --help${gl_bai} 显示此帮助信息"
echo -e ""
echo -e "${gl_lv}示例:${gl_bai}"
echo -e " ${gl_bai}$0 ${gl_lan}/compose${gl_bai} # 交互式确认后停止指定目录"
echo -e " ${gl_bai}$0 ${gl_lan}-y /compose${gl_bai} # 自动确认停止指定目录"
echo -e " ${gl_bai}$0${gl_bai} # 交互式选择目录"
echo -e ""
exit 0
}
main() {
local TARGET_DIR=""
local SKIP_CONFIRM="false"
while [[ $# -gt 0 ]]; do
case "$1" in
-y|--yes)
SKIP_CONFIRM="true"
shift
;;
-h|--help)
show_help
;;
*)
if [[ -z "$TARGET_DIR" ]]; then
TARGET_DIR="$1"
else
log_error "未知参数: $1"
echo -e "${gl_bai}使用 ${gl_huang}$0 -h ${gl_bai}查看帮助${gl_bai}"
exit 1
fi
shift
;;
esac
done
if [[ -n "$TARGET_DIR" ]]; then
stop_all_compose_projects "$TARGET_DIR" "$SKIP_CONFIRM"
else
interactive_stop
fi
}
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
main "$@"
fi
创建本地脚本
new_script="new_test.sh"
cat > "$new_script" << 'EOF'
#!/bin/bash
# 粘贴脚本源码
EOF
# 保留本地脚本,去掉 rm -f "$new_script"
chmod +x "$new_script" && ./"$new_script" && rm -f "$new_script"