AWS管理脚本:一键部署 Wavelength 环境 & 全能 EC2 换 IP 神器 (Zsh版)
如果你经常使用 AWS EC2,尤其是涉及到 AWS Wavelength (5G 频段) 或者是需要频繁更换实例公网 IP 的业务,你一定遇到过以下痛点:
Wavelength 配置极繁琐:要启用 Zone Group、创建 Carrier Gateway、配置路由表、反复重试寻找不冲突的子网 CIDR……手动搞一次至少 10 分钟。
换 IP 手速慢:想要换个 IP,得手动解绑 EIP、释放 EIP、申请新 EIP、绑定 EIP,一套流程下来手都酸了。
脚本兼容性差:网上的脚本在 Linux 上能跑,一到 macOS (Bash 3.2) 或者 AWS CloudShell 里就各种报错。
为了解决这些问题,我编写了一个 “AWS 瑞士军刀 (AWS Swiss Army Knife)” 脚本。它是基于原生 Zsh 编写的,无需安装任何依赖,在 MacBook 终端和 AWS CloudShell 上都能完美运行。
🛠️ 脚本核心功能
这个脚本将最常用的功能合二为一,并修复了所有已知的兼容性问题:
🌐 全球区域支持:支持动态获取 AWS 全球所有区域(不仅限于 Wavelength 区域),一键切换。
🚀 一键部署 Wavelength:全自动完成环境搭建。自动启用区域、自动复用/创建网关、智能避让网段冲突、自动配置路由表。
🔄 全能刷 IP 工具:
Wavelength 实例:自动循环更换 Carrier IP(运营商 IP)。
普通实例:自动循环更换标准 Public IP。
极速交互:按
Enter键秒换 IP,支持查重(不使用重复 IP)。
🛡️ 防崩溃设计:针对 AWS API 的延迟和偶发错误(如 CreateTags 失败、区域未就绪)做了完善的容错处理,极其稳定。
💻 如何使用
方法一:在 AWS CloudShell 中使用(推荐)
这是最简单的方法,不需要配置本地环境。
登录 AWS 控制台,点击右上角的终端图标打开 CloudShell。
输入
nano aws_tool.sh创建文件。将下方的脚本代码粘贴进去,按
Ctrl+O保存,Ctrl+X退出。赋予执行权限并运行:
Bashchmod +x aws_tool.sh ./aws_tool.sh
方法二:在 macOS 本地终端使用
确保你已经安装了 AWS CLI 并配置了凭证 (aws configure)。
打开终端 (Terminal)。
创建并运行脚本(步骤同上)。由于脚本指定了
#!/bin/zsh,它会自动使用 Mac 默认的 Zsh 解释器,无需担心 Bash 版本过旧的问题。
📜 脚本代码
(请复制以下代码保存为 aws_tool.sh)
#!/bin/zsh
# ============================================================
# AWS 瑞士军刀 (Zsh v9 UI 完美版)
# 优化: 明确显示“等待刷新”状态,不再显示莫名其妙的圆点
# 功能: 包含 v1-v8 所有修复 (全区域、防假死、逻辑修正)
# ============================================================
setopt shwordsplit
setopt no_nomatch
# ==============================
# 全局配置 & 颜色
# ==============================
GREEN='\033[0;32m'
BLUE='\033[0;34m'
YELLOW='\033[1;33m'
RED='\033[0;31m'
NC='\033[0m'
USED_IP_FILE="ip.txt"
# 初始化默认区域
export AWS_REGION="${AWS_REGION:-$(aws configure get region 2>/dev/null || echo 'us-east-1')}"
export AWS_DEFAULT_REGION="$AWS_REGION"
# ==============================
# 功能 0: 切换区域
# ==============================
function switch_region_menu() {
while true; do
clear
echo -e "${BLUE}=== 切换 AWS 区域 (Select Region) ===${NC}"
echo -e "当前: ${GREEN}$AWS_REGION${NC}"
echo
echo " --- 常用 Wavelength 区域 ---"
echo "1. US East (N. Virginia) [us-east-1] (Verizon)"
echo "2. US West (Oregon) [us-west-2] (Verizon)"
echo "3. Asia Pacific (Tokyo) [ap-northeast-1] (KDDI)"
echo "4. Asia Pacific (Seoul) [ap-northeast-2] (SKT)"
echo "5. Canada (Central) [ca-central-1] (Bell)"
echo "6. Europe (London) [eu-west-2] (Vodafone)"
echo
echo " --- 全球通用 ---"
echo "8. 📋 列出 AWS 所有可用区域 (动态获取)"
echo "9. ⌨️ 手动输入区域代码"
echo "0. 🔙 返回主菜单"
echo
echo -n "请选择 [0-9]: "
read -r r_choice
case $r_choice in
1) NEW_R="us-east-1" ;;
2) NEW_R="us-west-2" ;;
3) NEW_R="ap-northeast-1" ;;
4) NEW_R="ap-northeast-2" ;;
5) NEW_R="ca-central-1" ;;
6) NEW_R="eu-west-2" ;;
8)
echo
echo -n "正在连接 AWS 获取全球区域列表..."
ALL_REGIONS=$(aws ec2 describe-regions --query "Regions[].RegionName" --output text | tr '\t' '\n' | sort)
echo -e "\r\033[K"
echo -e "${YELLOW}=== AWS 全球区域列表 ===${NC}"
echo "$ALL_REGIONS" | pr -3 -t -w 80
echo
echo -n "请输入上方的一个区域代码 (例如 ap-east-1): "
read -r NEW_R
if [[ -z "$NEW_R" ]]; then continue; fi
;;
9)
echo -n "请输入区域代码 (如 us-east-2): "
read -r NEW_R
;;
0) return ;;
*) echo "无效选项"; sleep 1; continue ;;
esac
if [[ -n "$NEW_R" ]]; then
export AWS_REGION="$NEW_R"
export AWS_DEFAULT_REGION="$NEW_R"
echo -e "✅ 已切换至: ${GREEN}$NEW_R${NC}"
sleep 1
return
fi
done
}
# ==============================
# 功能 1: 部署环境
# ==============================
function deploy_wavelength() {
echo -e "${BLUE}>>> 进入 Wavelength 部署模式...${NC}"
(
set -euo pipefail
REGION="$AWS_REGION"
if [[ -z "$REGION" ]]; then echo -e "${RED}未检测到 Region。${NC}"; exit 1; fi
echo -e "当前区域: ${GREEN}$REGION${NC}"
echo; echo -e "${YELLOW}[1/5] 扫描并启用 Wavelength 区域...${NC}"
WL_AZS=$(aws ec2 describe-availability-zones --region "$REGION" --all-availability-zones --filters "Name=zone-type,Values=wavelength-zone" --query "AvailabilityZones[].ZoneName" --output text | tr '\t' '\n' | sort)
if [[ -z "$WL_AZS" ]]; then
echo -e "${RED}提示: 该区域 ($REGION) 没有检测到 Wavelength 资源。${NC}"
echo -e "此功能仅适用于 Wavelength 区域。"
echo -e "但您仍然可以使用 ${GREEN}[3. 刷换 IP]${NC} 功能来管理普通 EC2 实例。"
exit 0
fi
WL_GROUPS=$(aws ec2 describe-availability-zones --region "$REGION" --all-availability-zones --filters "Name=zone-type,Values=wavelength-zone" --query "AvailabilityZones[].GroupName" --output text | tr '\t' '\n' | sort -u)
for group in $WL_GROUPS; do
echo -n "启用组 $group ... "
aws ec2 modify-availability-zone-group --group-name "$group" --opt-in-status opted-in --region "$REGION" >/dev/null 2>&1 || true
echo -e "${GREEN}OK${NC}"
done
echo -n "⏳ 等待区域生效 (15秒)... "
sleep 15
echo -e "${GREEN}继续${NC}"
echo; echo -e "${YELLOW}[2/5] 获取 VPC 信息...${NC}"
VPC_INFO=$(aws ec2 describe-vpcs --region "$REGION" --query "Vpcs[].[VpcId, IsDefault, CidrBlock]" --output text)
if [[ -z "$VPC_INFO" ]]; then echo -e "${RED}无 VPC。${NC}"; exit 1; fi
TARGET_VPC=$(echo "$VPC_INFO" | grep "True" | head -n 1 || true)
if [[ -z "$TARGET_VPC" ]]; then TARGET_VPC=$(echo "$VPC_INFO" | head -n 1); fi
VPC_ID=$(echo "$TARGET_VPC" | awk '{print $1}')
VPC_CIDR=$(echo "$TARGET_VPC" | awk '{print $3}')
echo -e "选中 VPC: ${GREEN}$VPC_ID${NC} (CIDR: $VPC_CIDR)"
CIDR_PREFIX=$(echo "$VPC_CIDR" | cut -d'.' -f1,2)
echo; echo -e "${YELLOW}[3/5] 配置 Carrier Gateway${NC}"
EXISTING_CAGW=$(aws ec2 describe-carrier-gateways --filters "Name=vpc-id,Values=$VPC_ID" "Name=state,Values=available" --region "$REGION" --query "CarrierGateways[0].CarrierGatewayId" --output text)
if [[ "$EXISTING_CAGW" != "None" && -n "$EXISTING_CAGW" ]]; then
CAGW_ID="$EXISTING_CAGW"
echo -e "✅ 复用网关: ${GREEN}$CAGW_ID${NC}"
else
CAGW_ID=$(aws ec2 create-carrier-gateway --vpc-id "$VPC_ID" --region "$REGION" --query "CarrierGateway.CarrierGatewayId" --output text)
aws ec2 create-tags --resources "$CAGW_ID" --tags Key=Name,Value="Auto-Wavelength-CAGW" --region "$REGION"
echo -e "✅ 创建网关: ${GREEN}$CAGW_ID${NC}"
fi
echo -n "⏳ 等待网关就绪..."
while true; do
STATE=$(aws ec2 describe-carrier-gateways --carrier-gateway-ids "$CAGW_ID" --region "$REGION" --query "CarrierGateways[0].State" --output text)
if [[ "$STATE" == "available" ]]; then echo -e " ${GREEN}OK!${NC}"; break; else echo -n "."; sleep 2; fi
done
echo; echo -e "${YELLOW}[4/5] 配置路由表${NC}"
RT_ID=$(aws ec2 describe-route-tables --filters "Name=vpc-id,Values=$VPC_ID" "Name=tag:Name,Values=Wavelength-RouteTable-Auto" --region "$REGION" --query "RouteTables[0].RouteTableId" --output text)
if [[ "$RT_ID" != "None" && -n "$RT_ID" ]]; then
echo -e "复用路由表: ${GREEN}$RT_ID${NC}"
else
RT_ID=$(aws ec2 create-route-table --vpc-id "$VPC_ID" --region "$REGION" --query "RouteTable.RouteTableId" --output text)
aws ec2 create-tags --resources "$RT_ID" --tags Key=Name,Value="Wavelength-RouteTable-Auto" --region "$REGION"
echo -e "✅ 创建路由表: ${GREEN}$RT_ID${NC}"
fi
echo; echo -e "${YELLOW}[5/5] 创建子网并关联路由...${NC}"
CURRENT_OCTET=200
for az in $WL_AZS; do
echo -e " ----------------------------------------"
echo -e " 正在处理区域: ${BLUE}$az${NC}"
TARGET_SUBNET=""
EXISTING_SUBNET=$(aws ec2 describe-subnets --filters "Name=vpc-id,Values=$VPC_ID" "Name=availability-zone,Values=$az" --region "$REGION" --query "Subnets[0].SubnetId" --output text)
if [[ "$EXISTING_SUBNET" != "None" && -n "$EXISTING_SUBNET" ]]; then
echo -e " ✅ 已存在子网: $EXISTING_SUBNET (复用)"
TARGET_SUBNET="$EXISTING_SUBNET"
else
ATTEMPTS=0; MAX_ATTEMPTS=20; SUCCESS=false
while [ $ATTEMPTS -lt $MAX_ATTEMPTS ]; do
NEW_CIDR="${CIDR_PREFIX}.${CURRENT_OCTET}.0/24"
if [ $CURRENT_OCTET -gt 254 ]; then CURRENT_OCTET=100; NEW_CIDR="${CIDR_PREFIX}.${CURRENT_OCTET}.0/24"; fi
echo -n " 🛠 尝试创建网段 $NEW_CIDR ... "
CREATE_OUT=$(aws ec2 create-subnet --vpc-id "$VPC_ID" --cidr-block "$NEW_CIDR" --availability-zone "$az" --region "$REGION" --output json 2>/dev/null || echo "ERROR")
if [[ "$CREATE_OUT" == "ERROR" ]]; then
echo -e "${YELLOW}失败 -> 重试...${NC}"
CURRENT_OCTET=$((CURRENT_OCTET+1)); ATTEMPTS=$((ATTEMPTS+1)); sleep 1
else
TARGET_SUBNET=$(echo "$CREATE_OUT" | grep '"SubnetId":' | cut -d'"' -f4)
echo -e "${GREEN}成功! ($TARGET_SUBNET)${NC}"
sleep 2
SHORT_AZ=${az##*-}
aws ec2 create-tags --resources "$TARGET_SUBNET" --tags Key=Name,Value="Auto-Subnet-$SHORT_AZ" --region "$REGION" 2>/dev/null || true
CURRENT_OCTET=$((CURRENT_OCTET+1)); SUCCESS=true; break
fi
done
if [ "$SUCCESS" = false ]; then echo -e " ${RED}❌ 严重错误: 无法创建子网。${NC}"; continue; fi
fi
if [[ -n "$TARGET_SUBNET" ]]; then
echo -n " 🔗 关联路由表... "
aws ec2 associate-route-table --route-table-id "$RT_ID" --subnet-id "$TARGET_SUBNET" --region "$REGION" >/dev/null 2>&1 || true
echo -e "${GREEN}完成${NC}"
fi
done
echo; echo -e "${YELLOW}检查并添加最终路由...${NC}"
CURRENT_ROUTE_GW=$(aws ec2 describe-route-tables --route-table-id "$RT_ID" --region "$REGION" --query "RouteTables[0].Routes[?DestinationCidrBlock=='0.0.0.0/0'].GatewayId" --output text)
if [[ "$CURRENT_ROUTE_GW" != "None" && -n "$CURRENT_ROUTE_GW" && "$CURRENT_ROUTE_GW" != "$CAGW_ID" ]]; then
echo -e "${YELLOW}清理旧路由...${NC}"
aws ec2 delete-route --route-table-id "$RT_ID" --destination-cidr-block 0.0.0.0/0 --region "$REGION" 2>/dev/null || true
fi
OUTPUT=$(aws ec2 create-route --route-table-id "$RT_ID" --destination-cidr-block 0.0.0.0/0 --carrier-gateway-id "$CAGW_ID" --region "$REGION" 2>&1)
EXIT_CODE=$?
if [ $EXIT_CODE -eq 0 ] || echo "$OUTPUT" | grep -q "RouteAlreadyExists"; then
echo -e "✅ 路由添加成功 (0.0.0.0/0 -> $CAGW_ID)"
else
echo -e "${RED}❌ 路由添加失败: $OUTPUT ${NC}"
fi
echo; echo -e "${GREEN}🎉 部署完成!${NC}"
)
echo -e "${BLUE}按回车键返回主菜单...${NC}"
read
}
# ==============================
# 功能 2: 刷 IP 工具 (通用)
# ==============================
function run_ip_tool() {
touch "$USED_IP_FILE"
local REGION="$AWS_REGION"
if [[ -z "$REGION" ]]; then echo -e "${RED}未检测到 Region。${NC}"; return; fi
# 启动保活进程
(
while true; do
sleep 300
echo -e "\n\033[90m[防超时守护] $(date +%H:%M:%S) 终端保持活跃中...\033[0m" >&2
echo -n "按 [Enter] 刷IP | [q] 上级 | [m] 主菜单... " >&2
done
) &
local KEEP_ALIVE_PID=$!
function cleanup_ip_tool() {
kill $KEEP_ALIVE_PID 2>/dev/null || true
}
trap cleanup_ip_tool EXIT
# 定义辅助函数
region_from_az() { echo "$1" | cut -d- -f1-3; }
is_wavelength_az() { [[ "$1" == *wlz* ]]; }
normalize_nbg() { [[ "$1" == *a ]] && echo "${1%a}" || echo "$1"; }
# 普通实例
simple_swap_interactive() {
local INSTANCE_ID="$1"; local REGION="$2"
{
echo
echo "普通实例模式 ($INSTANCE_ID)"
echo "-------------------------------------"
echo " [Enter] 立即更换 IP"
echo " [q] 返回实例列表"
echo " [m] 返回主菜单"
echo "-------------------------------------"
} >&2
while true; do
echo -n "请操作 > " >&2
if ! read -r input; then break; fi
if [[ "$input" == "q" ]]; then return 0; fi
if [[ "$input" == "m" ]]; then return 10; fi
echo -e " [正在执行...]" >&2
while true; do
# 获取旧 IP 以便比对
local OLD_IP=$(aws ec2 describe-instances --instance-ids "$INSTANCE_ID" --query "Reservations[0].Instances[0].PublicIpAddress" --output text --region "$REGION")
local ALLOC_ID=$(aws ec2 describe-addresses --filters "Name=instance-id,Values=$INSTANCE_ID" --query "Addresses[0].AllocationId" --output text --region "$REGION")
if [[ "$ALLOC_ID" != "None" ]]; then
local ASSOC=$(aws ec2 describe-addresses --filters "Name=instance-id,Values=$INSTANCE_ID" --query "Addresses[0].AssociationId" --output text --region "$REGION")
aws ec2 disassociate-address --association-id "$ASSOC" --region "$REGION"
aws ec2 release-address --allocation-id "$ALLOC_ID" --region "$REGION"
fi
local NEW_ALLOC=$(aws ec2 allocate-address --domain vpc --query "AllocationId" --output text --region "$REGION")
aws ec2 associate-address --instance-id "$INSTANCE_ID" --allocation-id "$NEW_ALLOC" --region "$REGION" >/dev/null
local NEW_ASSOC=$(aws ec2 describe-addresses --allocation-ids "$NEW_ALLOC" --query "Addresses[0].AssociationId" --output text --region "$REGION")
aws ec2 disassociate-address --association-id "$NEW_ASSOC" --region "$REGION"
aws ec2 release-address --allocation-id "$NEW_ALLOC" --region "$REGION"
local NEW_IP="None"
# === 优化 UI: 明确显示等待 ===
echo -n "等待刷新" >&2
while [[ "$NEW_IP" == "None" || "$NEW_IP" == "$OLD_IP" ]]; do
sleep 1
NEW_IP=$(aws ec2 describe-instances --instance-ids "$INSTANCE_ID" --query "Reservations[0].Instances[0].PublicIpAddress" --output text --region "$REGION")
echo -n "." >&2
done
echo "" >&2
echo "获取 IP: $NEW_IP" >&2
if grep -Fq "$NEW_IP" "$USED_IP_FILE"; then echo "重复,重试..." >&2; continue; fi
echo "$(date +%F_%T) $NEW_IP" >> "$USED_IP_FILE"
break
done
done
}
# Wavelength 实例
wavelength_swap_interactive() {
local INSTANCE_ID="$1"; local NBG_RAW="$2"
local NBG="$(normalize_nbg "$NBG_RAW")"; local REGION="$(region_from_az "$NBG")"
{
echo
echo "Wavelength 模式 ($INSTANCE_ID)"
echo "-------------------------------------"
echo " [Enter] 立即更换 IP"
echo " [q] 返回实例列表"
echo " [m] 返回主菜单"
echo "-------------------------------------"
} >&2
while true; do
echo -n "请操作 > " >&2
if ! read -r input; then break; fi
if [[ "$input" == "q" ]]; then return 0; fi
if [[ "$input" == "m" ]]; then return 10; fi
echo -e " [正在执行...]" >&2
while true; do
local OLD_IP=$(aws ec2 describe-instances --instance-ids "$INSTANCE_ID" --query "Reservations[0].Instances[0].PublicIpAddress" --output text --region "$REGION")
if [[ "$OLD_IP" == "None" ]]; then
local DNS=$(aws ec2 describe-instances --instance-ids "$INSTANCE_ID" --query "Reservations[0].Instances[0].PublicDnsName" --output text --region "$REGION")
if [[ -n "$DNS" && "$DNS" != "None" ]]; then
local IP_PART="${DNS%%.*}"; IP_PART="${IP_PART#ec2-}"
OLD_IP="${IP_PART//-/.}"
fi
fi
local ALLOC_ID=$(aws ec2 describe-addresses --filters "Name=instance-id,Values=$INSTANCE_ID" --query "Addresses[0].AllocationId" --output text --region "$REGION")
if [[ "$ALLOC_ID" != "None" ]]; then
local ASSOC=$(aws ec2 describe-addresses --filters "Name=instance-id,Values=$INSTANCE_ID" --query "Addresses[0].AssociationId" --output text --region "$REGION")
echo "释放旧 IP..." >&2
aws ec2 disassociate-address --association-id "$ASSOC" --region "$REGION"
aws ec2 release-address --allocation-id "$ALLOC_ID" --network-border-group "$NBG" --region "$REGION"
fi
echo "分配新 IP..." >&2
local NEW_ALLOC=$(aws ec2 allocate-address --domain vpc --network-border-group "$NBG" --query "AllocationId" --output text --region "$REGION")
aws ec2 associate-address --instance-id "$INSTANCE_ID" --allocation-id "$NEW_ALLOC" --region "$REGION" >/dev/null
local NEW_ASSOC=$(aws ec2 describe-addresses --allocation-ids "$NEW_ALLOC" --query "Addresses[0].AssociationId" --output text --region "$REGION")
echo "解绑并释放..." >&2
aws ec2 disassociate-address --association-id "$NEW_ASSOC" --region "$REGION"
aws ec2 release-address --allocation-id "$NEW_ALLOC" --network-border-group "$NBG" --region "$REGION"
local NEW_IP="None"
# === 优化 UI: 明确显示等待 ===
echo -n "等待刷新" >&2
local retry=0
while [[ "$NEW_IP" == "None" || "$NEW_IP" == "$OLD_IP" ]]; do
sleep 1
local DNS=$(aws ec2 describe-instances --instance-ids "$INSTANCE_ID" --query "Reservations[0].Instances[0].PublicDnsName" --output text --region "$REGION")
if [[ -n "$DNS" && "$DNS" != "None" ]]; then
local IP_PART="${DNS%%.*}"; IP_PART="${IP_PART#ec2-}"
NEW_IP="${IP_PART//-/.}"
else
NEW_IP=$(aws ec2 describe-instances --instance-ids "$INSTANCE_ID" --query "Reservations[0].Instances[0].PublicIpAddress" --output text --region "$REGION")
fi
echo -n "." >&2
retry=$((retry+1))
if [[ $retry -gt 15 ]]; then break; fi
done
echo "" >&2
echo "IP -> $NEW_IP" >&2
if grep -Fq "$NEW_IP" "$USED_IP_FILE"; then echo "重复,重试..." >&2; continue; fi
echo "$(date +%F_%T) $NEW_IP" >> "$USED_IP_FILE"
break
done
done
}
select_instance() {
local REGION="$1"; local STATE_FILTER="${2:-running}"
local FILTER_ARGS=(); if [[ "$STATE_FILTER" != "all" ]]; then FILTER_ARGS+=(--filters "Name=instance-state-name,Values=$STATE_FILTER"); fi
local TMP_LIST="/tmp/aws_ec2_list_$$"
echo -n "正在连接 AWS 获取实例列表..." >&2
aws ec2 describe-instances "${FILTER_ARGS[@]}" --region "$REGION" --query "Reservations[].Instances[].[InstanceId,State.Name,Placement.AvailabilityZone,PublicIpAddress,PrivateIpAddress,Tags[?Key=='Name'].Value|[0]]" --output text | sed '/^$/d' > "$TMP_LIST"
echo -e "\r\033[K" >&2
if [[ ! -s "$TMP_LIST" ]]; then echo "❌ 未找到实例" >&2; rm -f "$TMP_LIST"; return 1; fi
local max_idx=$(wc -l < "$TMP_LIST" | tr -d ' ')
{
echo
echo "=== 实例列表 ($REGION) ==="
printf "%-4s %-20s %-10s %-20s %s\n" "No." "InstanceId" "State" "AZ" "PublicIP"
local idx=0; local line
while read -r line; do
idx=$((idx+1))
read -r IID STATE AZ PUB PRI NAME <<< "$line"
PUB="${PUB:-None}"
printf "%-4s %-20s %-10s %-20s %s\n" "$idx" "$IID" "$STATE" "$AZ" "$PUB"
done < "$TMP_LIST"
echo
} >&2
local choice
while true; do
echo -n "选择实例编号 (1-$max_idx): " >&2
if ! read -r choice; then rm -f "$TMP_LIST"; return 1; fi
if [[ "$choice" =~ ^[0-9]+$ ]] && (( choice >= 1 && choice <= max_idx )); then
local picked=$(sed -n "${choice}p" "$TMP_LIST")
read -r IID STATE AZ PUB PRI NAME <<< "$picked"
echo "$IID|$AZ"
rm -f "$TMP_LIST"; return 0
fi
done
}
echo -n "实例状态过滤 (running/all,默认 running): "
read -r STATE_FILTER
STATE_FILTER="${STATE_FILTER:-running}"
while true; do
local RESULT
if ! RESULT=$(select_instance "$REGION" "$STATE_FILTER"); then
echo "返回主菜单..."
cleanup_ip_tool
return
fi
local INSTANCE_ID="${RESULT%|*}"
local AZ="${RESULT#*|}"
local RET_CODE=0
if is_wavelength_az "$AZ"; then
local NBG="$(normalize_nbg "$AZ")"
wavelength_swap_interactive "$INSTANCE_ID" "$NBG"
RET_CODE=$?
else
simple_swap_interactive "$INSTANCE_ID" "$REGION"
RET_CODE=$?
fi
if [[ $RET_CODE -eq 10 ]]; then
cleanup_ip_tool
return
fi
echo "准备重新选择实例..."
sleep 1
done
}
# ==============================
# 主菜单
# ==============================
while true; do
clear
echo -e "${BLUE}========================================${NC}"
echo -e "${BLUE} AWS 瑞士军刀 (Zsh v9 UI 完美版) ${NC}"
echo -e "${BLUE}========================================${NC}"
echo -e "当前区域: ${GREEN}${AWS_REGION:-未设置}${NC}"
echo
echo "1. 切换区域 (Switch Region)"
echo "2. 部署环境 (Wavelength Only)"
echo "3. 刷换 IP (Universal IP Tool)"
echo "4. 退出 (Exit)"
echo
echo -n "请输入选项 [1-4]: "
read -r choice
case $choice in
1) switch_region_menu ;;
2) deploy_wavelength ;;
3) run_ip_tool ;;
4|q|exit) echo "再见!"; exit 0 ;;
*) echo "无效选项"; sleep 1 ;;
esac
done📸 功能演示
1. 主菜单
脚本启动后,界面清晰明了:
========================================
AWS 瑞士军刀 (Zsh v7 交互增强版)
========================================
当前区域: us-east-1
1. 切换区域 (Switch Region)
2. 部署环境 (Wavelength Only)
3. 刷换 IP (Universal IP Tool)
4. 退出 (Exit)
2. 部署 Wavelength 环境
选择 2,脚本会自动扫描并配置所有网络设施。你只需要看着屏幕上的绿色 OK 即可。针对 InternalError 或网段冲突,脚本会自动重试和避让。
3. 刷 IP 模式
选择 3,脚本会列出当前区域的所有实例。选择一个实例后进入交互模式:
按
Enter:立即更换一个新的 IP。按
q:返回上级列表选择其他机器。按
m:直接返回主菜单切换区域。
Wavelength 模式 (i-0xxxxxx)
-------------------------------------
[Enter] 立即更换 IP
[q] 返回实例列表
[m] 返回主菜单
-------------------------------------
请操作 > [正在执行...]
释放旧 IP...
分配新 IP...
解绑并释放...
IP -> 155.146.xx.xx
✅ 已记录:2025-12-23_10:00:00 155.146.xx.xx
💡 常见问题 (FAQ)
Q: 为什么要在 CloudShell 里用?
A: CloudShell 自带了 AWS CLI 和认证信息,网络直通 AWS 内网,延迟极低,刷 IP 速度最快。
Q: 普通 EC2 能用吗?
A: 可以!脚本会自动识别实例所在的子网类型。如果是普通区域,它会使用标准的 Allocate/Release Address 流程更换公网 IP。
Q: 脚本报错 trap: undefined signal: RETURN 怎么办?
A: 请确保使用的是最新版脚本(v3以上),我已经移除了不兼容的信号捕获,改用了标准的 EXIT 信号,完美兼容 macOS。