Files
dock/Docker容器管理面板
2026-01-22 21:02:33 +08:00

326 lines
11 KiB
Bash
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/bin/bash
# 简单实用的 Docker 容器管理脚本 (优化版)
# 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
PURPLE='\033[0;35m'
CYAN='\033[0;36m'
NC='\033[0m'
# 检查 Docker 及权限
check_docker() {
if ! command -v docker &> /dev/null; then
echo -e "${RED}错误: 未检测到 Docker请先安装。${NC}"
exit 1
fi
# 检查是否有权限执行 docker 命令
if ! docker ps &> /dev/null; then
echo -e "${RED}错误: 当前用户无权运行 Docker 命令。${NC}"
echo -e "${YELLOW}请尝试使用 sudo 运行此脚本,或将用户加入 docker 用户组。${NC}"
exit 1
fi
}
# 格式化输出表头
print_table_header() {
printf "${BLUE}%-5s | %-25s | %-12s | %-20s${NC}\n" "编号" "容器名称" "状态" "镜像"
echo "------|---------------------------|--------------|----------------------"
}
# 格式化输出行
print_table_row() {
printf "%-5s | %-25s | %-12b | %-20s\n" "$1" "$2" "$3" "$4"
}
# 显示容器列表
show_containers() {
echo -e "\n${BLUE}=== 容器列表 ===${NC}"
print_table_header
local count=0
# 获取数据并暂存避免管道导致的子shell变量丢失问题
mapfile -t lines < <(docker ps -a --format "table {{.Names}}\t{{.Status}}\t{{.Image}}" | tail -n +2)
for line in "${lines[@]}"; do
if [ -n "$line" ]; then
count=$((count + 1))
name=$(echo "$line" | awk '{print $1}')
status_raw=$(echo "$line" | awk '{print $2}')
image=$(echo "$line" | awk '{for(i=3;i<=NF;i++) printf $i" "; print ""}' | sed 's/ $//')
# 截断过长的名称以保持表格整洁
display_name=$name
if [ ${#display_name} -gt 23 ]; then display_name="${display_name:0:20}..."; fi
# 截断过长的镜像名
display_image=$image
if [ ${#display_image} -gt 18 ]; then display_image="${display_image:0:15}..."; fi
# 状态美化
if [[ "$status_raw" == "Up"* ]]; then
status_display="${GREEN}运行中${NC}"
elif [[ "$status_raw" == "Exited"* ]]; then
status_display="${RED}已停止${NC}"
else
status_display="${YELLOW}$status_raw${NC}"
fi
print_table_row "$count" "$display_name" "$status_display" "$display_image"
fi
done
echo
}
# 显示镜像列表 (优化对齐)
show_images() {
echo -e "\n${PURPLE}=== 镜像列表 ===${NC}"
printf "${PURPLE}%-5s | %-30s | %-12s | %-8s | %-15s${NC}\n" "编号" "镜像名称:标签" "镜像ID" "大小" "创建时间"
echo "------|--------------------------------|--------------|----------|----------------"
local count=0
mapfile -t lines < <(docker images --format "table {{.Repository}}:{{.Tag}}\t{{.ID}}\t{{.Size}}\t{{.CreatedSince}}" | tail -n +2)
for line in "${lines[@]}"; do
if [ -n "$line" ]; then
count=$((count + 1))
repo_tag=$(echo "$line" | awk '{print $1}')
image_id=$(echo "$line" | awk '{print $2}')
size=$(echo "$line" | awk '{print $3}')
created=$(echo "$line" | awk '{for(i=4;i<=NF;i++) printf $i" "; print ""}' | sed 's/ $//')
# 截断处理
if [ ${#repo_tag} -gt 28 ]; then repo_tag="${repo_tag:0:25}..."; fi
printf "%-5s | %-30s | %-12s | %-8s | %-15s\n" "$count" "$repo_tag" "$image_id" "$size" "$created"
fi
done
echo
}
# 通用获取名称函数 (复用逻辑)
get_name_by_index() {
local number=$1
local type=$2 # "container" or "image"
local cmd=""
if [ "$type" == "container" ]; then
cmd="docker ps -a --format '{{.Names}}'"
else
cmd="docker images --format '{{.ID}}'"
fi
# 动态获取第N行
eval "$cmd" | sed -n "${number}p"
}
# 进入容器终端 (新增功能)
enter_container() {
local container_name=$1
echo -e "${YELLOW}尝试进入容器: $container_name${NC}"
echo -e "提示: 输入 ${RED}exit${NC} 可退出容器终端"
echo
# 优先尝试 bash失败则尝试 sh
if docker exec -it "$container_name" /bin/bash 2>/dev/null; then
return
elif docker exec -it "$container_name" /bin/sh 2>/dev/null; then
return
else
echo -e "${RED}无法进入容器终端 (可能容器未包含 bash 或 sh)${NC}"
read -p "按回车键继续..."
fi
}
# 批量删除容器
batch_delete_containers() {
local numbers="$1"
local confirm="$2"
numbers=$(echo "$numbers" | sed 's/,/ /g')
local success_count=0
for number in $numbers; do
if [[ "$number" =~ ^[0-9]+$ ]]; then
container_name=$(get_name_by_index $number "container")
if [ -n "$container_name" ]; then
if [ "$confirm" != "y" ]; then
read -p "确认删除容器 $container_name? (y/N): " c
[[ "$c" != "y" && "$c" != "Y" ]] && continue
fi
if docker rm -f "$container_name" &> /dev/null; then
echo -e "${GREEN}✓ 删除成功: $container_name${NC}"
((success_count++))
else
echo -e "${RED}✗ 删除失败: $container_name${NC}"
fi
else
echo -e "${RED}✗ 编号 $number 不存在${NC}"
fi
fi
done
[ $success_count -gt 0 ] && echo -e "${GREEN}共删除 $success_count 个容器${NC}"
}
# 批量删除镜像
batch_delete_images() {
local numbers="$1"
local confirm="$2"
numbers=$(echo "$numbers" | sed 's/,/ /g')
local success_count=0
for number in $numbers; do
if [[ "$number" =~ ^[0-9]+$ ]]; then
image_id=$(get_name_by_index $number "image")
if [ -n "$image_id" ]; then
# 获取友好名称用于显示
image_name=$(docker images --format "{{.Repository}}:{{.Tag}}" | grep -v "REPOSITORY" | grep "$image_id" | head -n 1)
[ -z "$image_name" ] && image_name=$image_id
if [ "$confirm" != "y" ]; then
read -p "确认删除镜像 $image_name? (y/N): " c
[[ "$c" != "y" && "$c" != "Y" ]] && continue
fi
if docker rmi "$image_id" &> /dev/null; then
echo -e "${GREEN}✓ 删除成功: $image_name${NC}"
((success_count++))
else
echo -e "${RED}✗ 删除失败 (可能被容器占用): $image_name${NC}"
fi
fi
fi
done
}
# 清理悬空镜像
clean_dangling_images() {
local count=$(docker images -f "dangling=true" -q | wc -l)
if [ "$count" -gt 0 ]; then
read -p "发现 $count 个悬空镜像,是否清理?(y/N): " confirm
if [[ "$confirm" == "y" || "$confirm" == "Y" ]]; then
docker image prune -f
echo -e "${GREEN}清理完成${NC}"
fi
else
echo -e "${GREEN}系统很干净,没有悬空镜像。${NC}"
fi
read -p "按回车键继续..."
}
# 镜像管理菜单
image_management() {
while true; do
clear
echo -e "${PURPLE}========================================${NC}"
echo -e "${PURPLE} Docker 镜像管理${NC}"
echo -e "${PURPLE}========================================${NC}"
show_images
echo -e "${PURPLE}操作选项:${NC}"
echo "1. 删除镜像 (例如: 1 或 1,2,3)"
echo "2. 清空所有镜像 (慎用)"
echo "3. 清理悬空镜像 (<none>)"
echo "4. 返回主菜单"
echo
read -p "请选择: " choice
case $choice in
1)
read -p "请输入镜像编号: " input
batch_delete_images "$input" "n"
read -p "按回车键继续..."
;;
2)
read -p "警告:确定删除所有镜像吗?(yes/N): " confirm
if [ "$confirm" == "yes" ]; then
docker rmi -f $(docker images -q) 2>/dev/null
echo -e "${GREEN}已清空所有镜像${NC}"
fi
read -p "按回车键继续..."
;;
3) clean_dangling_images ;;
4) return ;;
*) echo -e "${RED}无效输入${NC}"; sleep 1 ;;
esac
done
}
# 主菜单
main_menu() {
while true; do
clear
echo -e "${BLUE}========================================${NC}"
echo -e "${BLUE} Docker 容器可视化管理 v2.0${NC}"
echo -e "${BLUE}========================================${NC}"
show_containers
echo -e "${BLUE}容器操作:${NC} ${CYAN}高级功能:${NC}"
printf "%-35s %-35s\n" "1. 启动容器" "7. 进入容器终端 (Exec)"
printf "%-35s %-35s\n" "2. 停止容器" "8. 实时资源监控 (Stats)"
printf "%-35s %-35s\n" "3. 重启容器" "9. 镜像管理菜单"
printf "%-35s\n" "4. 查看日志"
echo
echo -e "${RED}危险操作:${NC}"
echo "5. 删除容器 (例如: 1 或 1,2,3)"
echo "6. 清理所有已停止容器"
echo "0. 退出"
echo
read -p "请选择操作 [0-9]: " choice
# 处理需要选择容器的操作
if [[ "1 2 3 4 7" =~ "$choice" ]]; then
read -p "请输入容器编号: " num
if [[ ! "$num" =~ ^[0-9]+$ ]]; then
echo -e "${RED}请输入有效数字${NC}"; sleep 1; continue
fi
name=$(get_name_by_index $num "container")
if [ -z "$name" ]; then
echo -e "${RED}编号不存在${NC}"; sleep 1; continue
fi
case $choice in
1) docker start "$name" && echo -e "${GREEN}已启动 $name${NC}" ;;
2) docker stop "$name" && echo -e "${GREEN}已停止 $name${NC}" ;;
3) docker restart "$name" && echo -e "${GREEN}已重启 $name${NC}" ;;
4) docker logs -f --tail 100 "$name" ;;
7) enter_container "$name" ;;
esac
[ "$choice" != "7" ] && [ "$choice" != "4" ] && sleep 1
continue
fi
# 处理其他操作
case $choice in
5)
read -p "请输入要删除的容器编号 (支持批量): " nums
batch_delete_containers "$nums" "n"
read -p "按回车键继续..."
;;
6)
read -p "确认清理所有已停止的容器?(y/N): " c
if [[ "$c" == "y" || "$c" == "Y" ]]; then
docker container prune -f
echo -e "${GREEN}清理完成${NC}"
fi
sleep 1
;;
8)
echo -e "${CYAN}正在打开资源监控 (按 Ctrl+C 退出)...${NC}"
docker stats
;;
9) image_management ;;
0) echo -e "${GREEN}再见!${NC}"; exit 0 ;;
*) echo -e "${RED}无效选择${NC}"; sleep 1 ;;
esac
done
}
# 启动
check_docker
main_menu