一键脚本
bash <(curl -sL gitee.com/meimolihan/cmdbox/raw/master/sh/compose_all_up.sh) /vol1/1000/compose
bash <(curl -sL ../sh/compose_all_up.sh) /vol1/1000/compose
| 模式 | 命令 | 说明 |
|---|---|---|
| 默认模式 | ./脚本名.sh |
不传参,交互式选择工作目录 |
| 指定目录 | ./脚本名.sh /vol1/1000/compose |
启动指定目录下所有 Compose 项目 |
| 指定项目 | ./脚本名.sh /vol1/1000/compose 项目A 项目B |
仅启动指定项目,其余项目不动 |
| 自动确认 | ./脚本名.sh -y /compose |
-y 可放在任意位置,跳过确认直接启动 |
| 帮助 | ./脚本名.sh -h |
查看帮助 |
效果预览
补充说明
该脚本用于交互式批量启动指定目录下的 Docker Compose 项目,基于 docker-compose/docker compose 命令实现,支持一键启动全部项目,也可指定项目名精确定位启动,适合需要一键上线多个容器项目的场景。
功能特点
- 多种调用方式:无参、指定目录、指定项目、自动确认,参数任意组合
- 指定项目上线:目录后跟项目名仅启动列出的项目,其余项目不受影响
- 运行状态感知:菜单中区分运行中 / 已停止项目
- 推荐未运行目录:推荐包含未运行项目的预设目录并标记推荐
- 目录状态展示:显示每个预设目录的项目数与运行状态(均未运行 / 全部运行中 / 部分运行中)
- 二次确认机制:默认需确认后才执行,防止误操作
- -y 任意位置:-y/--yes 放开头、中间、末尾都生效,适合自动化脚本
- 完整统计信息:显示总计项目、成功启动数
输出说明
脚本输出包含以下字段:
| 字段 | 说明 |
|---|---|
| 工作目录 | 显示当前操作的 Compose 项目根目录 |
| 待处理项目列表 | 指定项目模式下显示要启动的项目名 |
| 扫描到的项目 | 列出扫描到的项目目录及运行状态(运行中 / 已停止) |
| 推荐的工作目录 | 标记含未运行项目的预设目录(★ 推荐) |
| 项目状态 | 显示每个项目的启动结果(✓ 已启动 / 启动失败) |
| 启动完成统计 | 总计项目、成功启动数 |
注意事项
- 脚本需要 Docker 服务运行以及 docker-compose/docker compose 命令可用
- 启动使用 up -d,已运行的项目不会重复创建容器
- 不传参时进入交互式目录选择菜单;可用
$0 .指定当前目录 - 指定项目模式下,目录下找不到 docker-compose.yml/yaml 的项目会自动跳过
- 交互菜单空输入回车时默认使用首个推荐目录
- 使用 -y/--yes 参数可跳过全部确认提示,且跳过"按任意键继续"等待
- 如果目录不存在或没有 Compose 文件,脚本会给出警告并退出
用法示例
-
不传任何参数,交互式菜单选择工作目录,具体确认当前目录
./compose_all_up.sh -
当前目录,免确认,直接全部上线
./compose_all_up.sh -y -
指定目录,交互式
./compose_all_up.sh /vol1/1000/compose -
指定目录,免确认,-y 放末尾也生效
./compose_all_up.sh /vol1/1000/compose -y -
指定目录 + 指定项目,免确认,仅上线项目A、项目B,其余项目不动
./compose_all_up.sh -y /vol1/1000/compose 项目A 项目B -
指定目录 + 指定项目名,交互式,仅上线项目A、项目B,其余项目不动
./compose_all_up.sh /vol1/1000/compose 项目A 项目B
脚本源码
#!/bin/bash
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() {
local frames=("⠋" "⠙" "⠹" "⠸" "⠼" "⠴" "⠦" "⠧" "⠇" "⠏")
local dots=(
"${gl_hong}."
"${gl_huang}."
"${gl_lv}."
"${gl_bufan}."
"${gl_zi}."
)
local dot_buffer=""
local frame_len=${#frames[@]}
local dot_idx=0
local total_dots=${#dots[@]}
for ((i=0; i<20; i++)); do
if (( i > 0 && i % 3 == 0 && dot_idx < total_dots )); then
dot_buffer+=${dots[$dot_idx]}
((dot_idx++))
fi
echo -ne "\r\033[K${gl_bufan}${frames[i % frame_len]}${gl_bai} 正在退出 ${dot_buffer}"
sleep_fractional 0.06
done
echo -e "\r\033[K${gl_lv}✓${gl_bai} 成功退出\n"
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
}
start_selected_compose_projects() {
local COMPOSE_WORK_DIR="$1"
shift
local PROJECT_LIST=("$@")
local SKIP_CONFIRM="${PROJECT_LIST[-1]}"
unset PROJECT_LIST[-1]
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_bai}待处理项目列表: ${gl_huang}${PROJECT_LIST[*]}${gl_bai}"
echo -e "${gl_bufan}————————————————————————————————————————————————${gl_bai}"
if [[ "$SKIP_CONFIRM" != "true" ]]; then
read -r -e -p "$(echo -e "${gl_hong}警告: ${gl_bai}确定启动上面列出项目吗?(${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
local started_count=0
local warn_count=0
local fail_count=0
local total_count=${#PROJECT_LIST[@]}
for proj in "${PROJECT_LIST[@]}"; do
local proj_dir="${COMPOSE_WORK_DIR}/${proj}"
local yml_file=""
if [[ -f "${proj_dir}/docker-compose.yml" ]]; then
yml_file="${proj_dir}/docker-compose.yml"
elif [[ -f "${proj_dir}/docker-compose.yaml" ]]; then
yml_file="${proj_dir}/docker-compose.yaml"
else
log_warn "项目 ${gl_huang}$proj${gl_bai} 目录下找不到 docker-compose.yml / yaml,跳过"
((warn_count++))
continue
fi
echo -e ""
echo -e "${gl_huang}>>> 启动项目: ${gl_lv}$proj${gl_bai}"
local out
local ret
if command -v docker-compose &>/dev/null; then
out=$(docker-compose -f "$yml_file" up -d)
ret=$?
elif docker compose version &>/dev/null; then
out=$(docker compose -f "$yml_file" up -d)
ret=$?
else
log_error "未找到 docker-compose / docker compose 命令"
return 1
fi
[[ -n "$out" ]] && echo "$out"
if [[ $ret -eq 0 ]]; then
log_ok "已完成: $proj"
((started_count++))
else
log_error "项目 $proj up 执行失败"
((fail_count++))
fi
echo -e ""
done
echo -e "${gl_bufan}————————————————————————————————————————————————${gl_bai}"
echo -e "${gl_bai}任务结束${gl_bai}"
echo -e "${gl_bai}总计项目: ${gl_huang}$total_count${gl_bai}"
echo -e "${gl_lv}成功启动: $started_count${gl_bai}"
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
}
start_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 1
fi
local started_count=0
local total_count=0
echo -e "${gl_bai}正在扫描 ${gl_huang}$COMPOSE_WORK_DIR${gl_bai} 下的 Compose 项目${gl_bai}"
echo -e "${gl_bufan}————————————————————————————————————————————————${gl_bai}"
if [[ -f "docker-compose.yml" ]] || [[ -f "docker-compose.yaml" ]]; then
((total_count++))
local current_dir_name=$(basename "$(pwd)")
echo -e ""
echo -e "${gl_huang}>>> 启动项目: ${gl_lv}$current_dir_name${gl_bai} (当前目录)"
local out
local ret
if command -v docker-compose &>/dev/null; then
out=$(docker-compose up -d)
ret=$?
elif docker compose version &>/dev/null; then
out=$(docker compose up -d)
ret=$?
else
log_error "未找到 docker-compose 命令"
return 1
fi
[[ -n "$out" ]] && echo "$out"
if [[ $ret -eq 0 ]]; then
log_ok "已启动: $current_dir_name"
((started_count++))
else
log_error "启动失败: $current_dir_name"
fi
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
((total_count++))
local project_name=$(basename "$dir")
echo -e ""
echo -e "${gl_huang}>>> 启动项目: ${gl_lv}$project_name${gl_bai}"
local out
local ret
if command -v docker-compose &>/dev/null; then
out=$(docker-compose up -d)
ret=$?
elif docker compose version &>/dev/null; then
out=$(docker compose up -d)
ret=$?
else
log_error "未找到 docker-compose 命令"
cd ..
continue
fi
[[ -n "$out" ]] && echo "$out"
if [[ $ret -eq 0 ]]; then
log_ok "已启动: $project_name"
((started_count++))
else
log_error "启动失败: $project_name"
fi
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_lv}成功启动: $started_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_start() {
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_bai}"
local all_projects=()
for preset_dir in "${preset_dirs[@]}"; do
if [[ -d "$preset_dir" ]]; then
if [[ -f "$preset_dir/docker-compose.yml" ]] || [[ -f "$preset_dir/docker-compose.yaml" ]]; then
all_projects+=("$preset_dir")
fi
for dir in "$preset_dir"/*/; do
if [[ -d "$dir" ]] && ([[ -f "$dir/docker-compose.yml" ]] || [[ -f "$dir/docker-compose.yaml" ]]); then
all_projects+=("$(realpath "$dir")")
fi
done
fi
done
all_projects=($(printf "%s\n" "${all_projects[@]}" | sort -u))
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
local has_projects=false
for project in "${all_projects[@]}"; do
if [[ "$project" == "$preset_dir" ]] || [[ "$project" == "$preset_dir"* ]]; then
has_projects=true
break
fi
done
if $has_projects; then
local all_running=true
for project in "${all_projects[@]}"; do
if [[ "$project" == "$preset_dir" ]] || [[ "$project" == "$preset_dir"* ]]; then
local is_running=false
for running_dir in "${running_projects[@]}"; do
if [[ "$running_dir" == "$project" ]]; then
is_running=true
break
fi
done
if ! $is_running; then
all_running=false
break
fi
fi
done
if ! $all_running; then
recommended_dirs+=("$preset_dir")
fi
fi
done
if [[ ${#all_projects[@]} -gt 0 ]]; then
echo -e "${gl_bai}扫描到的 docker-compose 项目目录:${gl_bai}"
for dir in "${all_projects[@]}"; do
local is_running=""
for running_dir in "${running_projects[@]}"; do
if [[ "$running_dir" == "$dir" ]]; then
is_running=" ${gl_lv}[运行中]${gl_bai}"
break
fi
done
[[ -z "$is_running" ]] && is_running=" ${gl_huang}[已停止]${gl_bai}"
echo -e " ${gl_lv}•${gl_bai} ${dir}$is_running"
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
local running_count=0
for project in "${running_projects[@]}"; do
if [[ "$project" == "${preset_dirs[i]}" ]] || [[ "$project" == "${preset_dirs[i]}"/* ]]; then
running_count=$((running_count + 1))
fi
done
if [[ $running_count -eq 0 ]]; then
dir_status="${gl_huang}[${compose_count}个项目,均未运行]${gl_bai}"
elif [[ $running_count -eq $compose_count ]]; then
dir_status="${gl_lv}[${compose_count}个项目,全部运行中]${gl_bai}"
else
dir_status="${gl_huang}[${compose_count}个项目,${running_count}个运行中]${gl_bai}"
fi
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
start_all_compose_projects "$COMPOSE_WORK_DIR" "false"
}
show_help() {
echo -e "${gl_lv}使用说明:${gl_bai}"
echo -e " ${gl_bai}$0 ${gl_huang}[-y] ${gl_lan}[目录] [项目1 项目2 ...]${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_bai} # 不传参,交互式选择目录"
echo -e " ${gl_bai}$0 .${gl_bai} # 当前目录,交互式确认,启动全部compose"
echo -e " ${gl_bai}$0 -y .${gl_bai} # 当前目录免确认,全部上线"
echo -e " ${gl_bai}$0 /vol1/1000/compose${gl_bai} # 指定目录交互式启动全部"
echo -e " ${gl_bai}$0 /vol1/1000/compose -y${gl_bai} # 指定目录,-y放末尾免确认"
echo -e " ${gl_bai}$0 -y /vol1/1000/compose projA projB${gl_bai} # 仅启动projA projB,免确认"
echo -e " ${gl_bai}$0 /vol1/1000/compose projA projB${gl_bai} # 仅启动projA projB,交互式确认"
echo -e ""
exit 0
}
main() {
local SKIP_CONFIRM="false"
local positional=()
for arg in "$@"; do
case "$arg" in
-y|--yes)
SKIP_CONFIRM="true"
;;
-h|--help)
show_help
;;
*)
positional+=("$arg")
;;
esac
done
if [[ ${#positional[@]} -ge 1 ]]; then
local TARGET_DIR="${positional[0]}"
local PROJECTS=("${positional[@]:1}")
if [[ ${#PROJECTS[@]} -ge 1 ]]; then
start_selected_compose_projects "$TARGET_DIR" "${PROJECTS[@]}" "$SKIP_CONFIRM"
else
start_all_compose_projects "$TARGET_DIR" "$SKIP_CONFIRM"
fi
else
interactive_start
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"