created by Dave on 16Jan.2025
Updated on 6Feb.2025 ver1.3
Updated on 12Apr.2025 ver1.4
- added option 8. Watching container logs 10.
- added option 9. Create new image from container
注:已经从最早发布的 3 个菜单版本,改到 7 菜单版本。8.添加 Entry container shell 功能
前言:
当昨天在文章中,露出了 $2 域名 daven.us , 这个域名是可以在公网上访问。 当初为了方便,但任何人都能在主页点 “编辑导航” 去编辑内容。刚添加了IP限制。还改了之前的保存会替换图片的错误,还有一个什么忘了。
修改文件后,要上传到 docker。每次都要敲几次键盘,用脚本替代这些重复操作。
然后想实现的功能,也随着越用也越多。最后是因为名字 docker file copy,增加了从 Container 上下载的功能,最后改为现在版本的样子。
都这么多了,添加第三选项:重启容器;
第四个选项:停止容器;
第五个选项:删除容器与关联 image
第 6 选项:返回选择另一个容器
脚本:
环境:QNAP NAS, QTS-5.x
一、使用方式:
直接运行脚本 dcokerfilescopy.sh :
updated picture on 12Apr.2025.
它猫的,SecureCRT 不能显示颜色。网上抄的这功能没在上图表现出来。大圆点是 绿/红 。
二、功能描述:
- 列出当前主机的所有 Containers 容器
- 容器 Containers 按运行与停止分为两部分,显示在屏幕的上部与下部
- 简化容器的显示信息,只显示:序号,Name,Image,ID,Ports 五个部分,并以此顺序显示
- 使用序号输入,或是 0 退出程序。
- 如果选择的容器是 停止的,会询问启动。
- 输入序号扣,会显示选择的 容器 Container 信息,来确认。 确认使用 Yes/No
- 提示“请选择操作”: 1. 上传文件到容器 2.从容器下载文件 3.重启容器 4.停止容器 5.删除容器与之关联 image 6.选择另一个容器 7.停止容器 .退出
- 当选择 1. 上传文件到容器
- 就会执行文件复制到 容器 Container 的操作。但排除 venv 目录,与所有 "点/dot/."开始的文件与目录
- 复制结束后,会询问 是否重启容器,并列出 容器的名字与ID。确认用 Yes/No
- 重启后会显示,成功。
- 判断容器是否运行。
- 显示运行结果:“文件复制完成” “操作完成,未重启容器” “容器重启成功" "容器正在运行" "操作成功完成” 这几个状态。
- 完成后返回 提示“请选择操作”: 1. 上传文件到容器 2.从容器下载文件 3.重启容器 4.停止容器 5.删除容器与之关联 image 6.选择另一个容器 7.进入容器Shell 8.退出
- 当选择 2.从容器下载文件
- 先会在脚本运行目录上,创建一个 download_from_docker.container 子目录
- 然后执行从 容器 Container 复制全部文件与目录 到 download_from_docker.container 目录下。
- 完成后返回 提示“请选择操作”: 1. 上传文件到容器 2.从容器下载文件 3.重启容器 4.停止容器 5.删除容器与之关联 image 6.选择另一个容器 7.进入容器Shell 8.退出
- 当选择 3.重启容器
- 会立即执行 Container 容器的重启操作。
- 重启后会显示,成功。
- 判断容器是否运行。
- 完成后返回 提示“请选择操作”: 1. 上传文件到容器 2.从容器下载文件 3.重启容器 4.停止容器 5.删除容器与之关联 image 6.选择另一个容器 7.进入容器Shell 8.退出
- 当选择 4.停止容器
- 会立即执行 Container 容器的停止操作
- 容器停止后会显示,成功
- 会判断容器是否运行。
- 完成后返回 提示“请选择操作”: 1. 上传文件到容器 2.从容器下载文件 3.重启容器 4.停止容器 5.删除容器与之关联 image 6.选择另一个容器 7.进入容器Shell 8.退出
- 选择 5. 删除容器与之关联 image
- 会询问是否继续 y/n
- (字面意思)
- 完成后返回 容器选择
- 选择 6. 选择另一个容器
- 选择 7.进入容器的shell
- 退出会返回 提示“请选择操作”: 1. 上传文件到容器 2.从容器下载文件 3.重启容器 4.停止容器 5.删除容器与之关联 image 6.选择另一个容器 7.进入容器Shell 8.退出
- added 选择 8. 监控 Container 日志输出。 按 ctrl+c 退出
- added 选择 9. 使用当前 Container 制作新镜像
- 选择
810 会退出脚本循环。- 返回 “程序结束”
- 列表格式,基于我 laptop 显示器调整的。
- 最后上传的是英文菜单版本。
- 看别人的脚本,加入了进度计数。
上传的版本使用中文提示语。
三、完整 dockerfilecopy.sh 文件内容
#!/bin/bash
# Created by Dave on 16Jan.2025
# Version 1.3 updated 6Feb.2025
# Version 1.4 updated on 12April2025
# Description: to copy files and directories to/from container and restart it.
# Updated History:
# 1.3 updated 6Feb.2025 added clear screen and menue 0 to display containers. 0 to refresh the container list and 99 to exit the program.
# 1.4 added Watching Docker logs and Create Image from container in option 8 and 9.
# error handling
set -euo pipefail
# Set UTF-8 encoding
export LANG=en_US.UTF-8
# Global variables
declare -a container_ids
declare -a container_names
declare -a container_images
declare -a container_ports
declare -a container_status
declare -i total_containers=0
readonly TIMEOUT=30
readonly DOWNLOAD_DIR="download_from_docker.container"
# Functions
handle_error() {
echo "Error: $1" >&2
exit 1
}
trap 'echo -e "\nOperation interrupted"; exit 1' INT TERM
check_container_access() {
local container_id=$1
if ! docker inspect "$container_id" >/dev/null 2>&1; then
handle_error "Cannot access container: $container_id"
fi
}
check_disk_space() {
local required_space=$1 # in MB
local available_space
available_space=$(df -m . | awk 'NR==2 {print $4}')
if [ "${available_space}" -lt "${required_space}" ]; then
handle_error "Insufficient disk space. Required: ${required_space}MB, Available: ${available_space}MB"
fi
}
display_containers() {
clear
echo "=================================================== Docker Containers Status ==================================================="
echo -e "\033[1m🟢 Running Containers:\033[0m"
printf "%2s │ %-30s │ %-35s │ %-12s │ %-40s\n" "No" "Name" "Image" "ID" "Ports"
echo "───┼────────────────────────────────┼─────────────────────────────────────┼──────────────┼──────────────────────────────────────"
container_ids=()
container_names=()
container_images=()
container_ports=()
container_status=()
local count=1
while read -r line; do
if [ -n "$line" ]; then
IFS='|' read -r id name image ports <<< "$line"
printf "%2d │ %-30s │ %-35s │ %-12s │ %-40s\n" \
"$count" "$name" "$image" "$id" "$ports"
container_ids[$count-1]="$id"
container_names[$count-1]="$name"
container_images[$count-1]="$image"
container_ports[$count-1]="$ports"
container_status[$count-1]="running"
((count++))
fi
done < <(docker ps --format "{{.ID}}|{{.Names}}|{{.Image}}|{{.Ports}}" 2>/dev/null || handle_error "Failed to get running containers")
echo -e "\n\033[1m🔴 Stopped Containers:\033[0m"
printf "%2s │ %-30s │ %-35s │ %-12s │ %-40s\n" "No" "Name" "Image" "ID" "Ports"
echo "───┼────────────────────────────────┼─────────────────────────────────────┼──────────────┼──────────────────────────────────────"
while read -r line; do
if [ -n "$line" ]; then
IFS='|' read -r id name image ports <<< "$line"
printf "%2d │ %-30s │ %-35s │ %-12s │ %-40s\n" \
"$count" "$name" "$image" "$id" "$ports"
container_ids[$count-1]="$id"
container_names[$count-1]="$name"
container_images[$count-1]="$image"
container_ports[$count-1]="$ports"
container_status[$count-1]="stopped"
((count++))
fi
done < <(docker ps -f "status=exited" --format "{{.ID}}|{{.Names}}|{{.Image}}|{{.Ports}}" 2>/dev/null || handle_error "Failed to get stopped containers")
echo "════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════"
total_containers=$((count-1))
if [ $total_containers -eq 0 ]; then
handle_error "No containers found"
fi
}
display_selected_container() {
local index=$1
echo -e "\nSelected Container Information:"
echo "────────────────────────────────────"
printf "Number: %d\n" "$index"
printf "Name: %s\n" "${container_names[$index-1]}"
printf "Image: %s\n" "${container_images[$index-1]}"
printf "Container ID: %s\n" "${container_ids[$index-1]}"
printf "Ports: %s\n" "${container_ports[$index-1]}"
printf "Status: %s\n" "${container_status[$index-1]}"
echo "────────────────────────────────────"
}
restart_container() {
local container_id=$1
local container_name="${container_names[$container_number-1]}"
echo "Starting container restart: $container_name (ID: $container_id)"
if ! docker restart "$container_id"; then
handle_error "Failed to restart container"
fi
echo "Waiting for container to be ready..."
local counter=0
while [ $counter -lt $TIMEOUT ]; do
if docker ps -q --filter "id=$container_id" | grep -q .; then
echo "Container is running"
return 0
fi
sleep 1
((counter++))
echo -n "."
done
handle_error "Container failed to start within ${TIMEOUT} seconds"
}
download_from_container() {
local container_id=$1
local container_name="${container_names[$container_number-1]}"
echo -e "\nStarting file download from container..."
echo "Container name: $container_name (ID: $container_id)"
check_container_access "$container_id"
check_disk_space 100
if [ -d "$DOWNLOAD_DIR" ]; then
echo "Removing existing download directory..."
rm -rf "$DOWNLOAD_DIR" || handle_error "Failed to remove existing download directory"
fi
echo "Creating new download directory..."
mkdir -p "$DOWNLOAD_DIR" || handle_error "Failed to create download directory"
echo "Getting file list from container..."
if ! docker exec "$container_id" find /app -type f > /tmp/container_files.txt; then
handle_error "Failed to get file list from container"
fi
if ! docker exec "$container_id" find /app -type d > /tmp/container_dirs.txt; then
handle_error "Failed to get directory list from container"
fi
echo "Creating directory structure..."
while IFS= read -r dir; do
local relative_dir=${dir#/app/}
if [ -n "$relative_dir" ]; then
mkdir -p "$DOWNLOAD_DIR/$relative_dir" || handle_error "Failed to create directory: $relative_dir"
fi
done < /tmp/container_dirs.txt
echo "Downloading files..."
local total_files=$(wc -l < /tmp/container_files.txt)
local current_file=0
while IFS= read -r file; do
local relative_file=${file#/app/}
if [ -n "$relative_file" ]; then
((current_file++))
printf "Downloading (%d/%d): %s\n" "$current_file" "$total_files" "$relative_file"
if ! docker cp "$container_id:$file" "$DOWNLOAD_DIR/$relative_file"; then
echo "Warning: Failed to copy file: $relative_file"
fi
fi
done < /tmp/container_files.txt
rm -f /tmp/container_files.txt /tmp/container_dirs.txt
echo "Download completed. Files saved in: $DOWNLOAD_DIR/"
}
upload_to_container() {
local container_id=$1
local total_files=0
local current_file=0
echo -e "\nStarting file upload to container..."
check_container_access "$container_id"
total_files=$(find . -type f -not -path "./venv/*" -not -path "./dockerfilecopy.sh" \
-not -path "*/.*" -not -path "./${DOWNLOAD_DIR}/*" \
-not -path "./Start_app.bat" | wc -l)
find . -type f -not -path "./venv/*" -not -path "./dockerfilecopy.sh" \
-not -path "*/.*" -not -path "./${DOWNLOAD_DIR}/*" -print0 | \
while IFS= read -r -d '' file; do
((current_file++))
printf "Uploading (%d/%d): %s\n" "$current_file" "$total_files" "$file"
if [ ! -r "$file" ]; then
echo "Warning: Cannot read file: $file"
continue
fi
if ! docker cp "$file" "$container_id:/app/${file#./}"; then
echo "Warning: Failed to copy file: $file"
fi
done
echo "Uploading directories..."
find . -type d -not -path "./venv" -not -path "./venv/*" -not -path "." \
-not -path "*/.*" -not -path "./${DOWNLOAD_DIR}" \
-not -path "./${DOWNLOAD_DIR}/*" -print0 | \
while IFS= read -r -d '' dir; do
echo "Copying directory: $dir"
if ! docker cp "$dir" "$container_id:/app/"; then
echo "Warning: Failed to copy directory: $dir"
fi
done
echo "File upload completed"
}
stop_container() {
local container_id=$1
local container_name="${container_names[$container_number-1]}"
echo "Stopping container: $container_name (ID: $container_id)"
if ! docker stop "$container_id"; then
handle_error "Failed to stop container"
fi
echo "Waiting for container to stop..."
local counter=0
while [ $counter -lt $TIMEOUT ]; do
if ! docker ps -q --filter "id=$container_id" | grep -q .; then
echo "Container stopped successfully"
return 0
fi
sleep 1
((counter++))
echo -n "."
done
handle_error "Container failed to stop within ${TIMEOUT} seconds"
}
delete_container_and_image() {
local container_id=$1
local container_name="${container_names[$container_number-1]}"
local container_image="${container_images[$container_number-1]}"
echo "Preparing to delete container: $container_name (ID: $container_id)"
# Check if container is running and stop it first
if [ "${container_status[$container_number-1]}" = "running" ]; then
echo "Container is running. Stopping it first..."
if ! stop_container "$container_id"; then
handle_error "Failed to stop container before deletion"
fi
fi
# Delete container
echo "Deleting container..."
if ! docker rm "$container_id"; then
handle_error "Failed to delete container"
fi
# Delete image
echo "Deleting associated image: $container_image"
if ! docker rmi "$container_image"; then
echo "Warning: Failed to delete image. It might be used by other containers."
fi
echo "Container and image deletion completed"
}
enter_container() {
local container_id=$1
local container_name="${container_names[$container_number-1]}"
echo "Entering container: $container_name (ID: $container_id)"
if [ "${container_status[$container_number-1]}" = "stopped" ]; then
echo "Container is stopped. Please start it first."
return 1
fi
echo "Starting interactive shell..."
if ! docker exec -it "$container_id" /bin/bash; then
# Try with sh if bash is not available
if ! docker exec -it "$container_id" /bin/sh; then
handle_error "Failed to enter container. Neither bash nor sh shell available."
fi
fi
}
watching_container_logs() {
local container_id=$1
local container_name="${container_names[$container_number-1]}"
echo "Following logs for container: $container_name (ID: $container_id)"
# 更明确地禁用中断陷阱
trap '' INT
if [ "${container_status[$container_number-1]}" = "stopped" ]; then
echo "Warning: Container is stopped. Logs might be limited."
fi
echo "Press Ctrl+C to stop following logs and return to menu"
sleep 1
# 启动docker logs命令
docker logs -f "$container_id" || true
echo "Returning to menu..."
# 重新启用中断陷阱
trap 'echo -e "\nOperation interrupted"; exit 1' INT TERM
}
create_image_from_container() {
local container_id=$1
local container_name="${container_names[$container_number-1]}"
local container_image="${container_images[$container_number-1]}"
echo -e "\nCreating new image from container: $container_name (ID: $container_id)"
# 询问新镜像的名称和标签
echo -n "Enter new image name [default: ${container_image%:*}-new]: "
read -r new_image_name
# 如果用户没有输入,使用默认值
if [ -z "$new_image_name" ]; then
new_image_name="${container_image%:*}-new"
fi
echo -n "Enter tag [default: latest]: "
read -r tag
# 如果用户没有输入,使用默认值
if [ -z "$tag" ]; then
tag="latest"
fi
# 完整的新镜像名称
local full_new_image="$new_image_name:$tag"
echo "Creating new image: $full_new_image from container $container_name..."
# 使用docker commit命令创建新镜像
if ! docker commit "$container_id" "$full_new_image"; then
handle_error "Failed to create new image from container"
fi
echo "New image created successfully: $full_new_image"
# 询问是否要删除旧镜像
echo -n "Do you want to remove the old image ($container_image)? (y/n): "
read -r remove_old
case $remove_old in
[Yy]* )
echo "Removing old image: $container_image"
if ! docker rmi "$container_image"; then
echo "Warning: Failed to remove old image. It might be used by other containers."
else
echo "Old image removed successfully"
fi
;;
* )
echo "Old image retained"
;;
esac
}
# menu
show_menu() {
echo -e "\nPlease select operation:"
echo "0. Display containers"
echo "1. Upload files to container"
echo "2. Download files from container"
echo "3. Restart container"
echo "4. Stop container"
echo "5. Delete container and image"
echo "6. Select another container"
echo "7. Enter container shell"
echo "8. Watching container logs"
echo "9. Create new image from container"
echo "10. Exit"
echo -n "Enter option (0-9): "
}
# Main
main() {
display_containers
while true; do
echo -n "Enter container number (1-$total_containers, Refresh 0, EXIT: 99): "
read -r container_number
if [ "$container_number" = "0" ]; then
display_containers
continue
fi
if [ "$container_number" = "99" ]; then
echo "Program terminated"
exit 0
fi
if ! [[ "$container_number" =~ ^[0-9]+$ ]] || \
[ "$container_number" -lt 1 ] || \
[ "$container_number" -gt "$total_containers" ]; then
echo "Error: Please enter a valid number (0 to exit, 1-$total_containers)"
continue
fi
display_selected_container "$container_number"
echo -n "Confirm selection? (y=1/n=any): "
read -r confirm
case $confirm in
[yY1]* )
container_id="${container_ids[$container_number-1]}"
break
;;
* )
echo "Cancelled, please select again"
;;
esac
done
if [ "${container_status[$container_number-1]}" = "stopped" ]; then
echo "Warning: Container is stopped"
echo -n "Start container? (y=1/n=any): "
read -r start_choice
case $start_choice in
[Yy1]* )
if ! docker start "$container_id"; then
handle_error "Failed to start container"
fi
echo "Container started successfully"
sleep 2
;;
* )
echo "Operation cancelled"
exit 1
;;
esac
fi
# Main menu loop
while true; do
show_menu
read -r choice
case $choice in
0)
display_containers
;;
1)
upload_to_container "$container_id"
while true; do
echo -n "Restart container? (y/n): "
read -r restart_choice
case $restart_choice in
[Yy]* )
if restart_container "$container_id"; then
echo "Operation completed successfully"
else
echo "Operation completed with restart errors"
fi
break
;;
[Nn]* )
echo "Operation completed without container restart"
break
;;
* )
echo "Please enter y or n"
;;
esac
done
;;
2)
download_from_container "$container_id"
;;
3)
echo "Restarting container..."
if restart_container "$container_id"; then
echo "Container restart completed successfully"
else
echo "Container restart failed"
fi
;;
4)
if [ "${container_status[$container_number-1]}" = "stopped" ]; then
echo "Container is already stopped"
else
echo "Stopping container..."
if stop_container "$container_id"; then
container_status[$container_number-1]="stopped"
echo "Container stop completed successfully"
else
echo "Container stop failed"
fi
fi
;;
5)
echo -n "Are you sure you want to delete the container:${container_names[$container_number-1]} and its image:${container_images[$container_number-1]}? (y/n): "
read -r delete_confirm
case $delete_confirm in
[Yy]* )
delete_container_and_image "$container_id"
echo "Returning to container selection..."
main
;;
* )
echo "Deletion cancelled"
;;
esac
;;
6)
echo "Backing to container selection..."
clear
main
;;
7)
if enter_container "$container_id"; then
echo "Returning to container selection..."
else
echo "Failed to enter container"
fi
;;
8)
echo "Watching container logs (press Ctrl+C to return to menu)..."
watching_container_logs "$container_id"
;;
9)
create_image_from_container "$container_id"
;;
10)
echo "Program terminated"
exit 0
;;
*)
echo "Invalid option, please try again"
;;
esac
done
}
main
提示:
- 使用 chmod +x 给脚本执行权力 后运行
四、结束
这是写的并以文章发出的第4个 shell 批处理文件。个人的兴趣爱好,因为重复繁琐,所以编出的乱码。
- 我用的 bash shell
- 使用别人的脚本前,先查看脚本逻辑。
- 本人不承担因脚本运行造成的损失。
- 使用 AI 翻译成中文时,格式有错误,上传替换为英文菜单版本。
- 除非主菜单有变化,才会更新 脚本文件。
笑话:
拿到一台 Linux 系统。
第一件事:删除法语语言包。 一定要在 root 用户下操作~
rm -fr /
别真做。再发一张美图。
Updated on 6Feb.2025
# 1.3 updated 6Feb.2025 added clear screen and menue 0 to display containers. 0 to refresh the container list and 99 to exit the program.
and posted full code in this article
Updated on 12Apr.2025
# Ver1.4 added Watching Docker logs and Create Image from container in option 8 and 9.