AWS Cloudshell一键更换EC2实例Elastic IP(含运营商IP)脚本
合并脚本,自动获取实例列表:
#!/bin/zsh
# ==========================================
# AWS EIP 自动更换工具 (Zsh Native 修复版)
# 修复了 IP 地址带反斜杠的问题
# ==========================================
setopt shwordsplit
setopt no_nomatch
set -euo pipefail
USED_IP_FILE="ip.txt"
touch "$USED_IP_FILE"
REGION="${AWS_REGION:-$(aws configure get region 2>/dev/null || true)}"
if [[ -z "$REGION" ]]; then
echo "❌ 未检测到 Region。"
echo "请先运行: export AWS_REGION=us-east-1"
exit 1
fi
start_keep_alive() {
(
while true; do
sleep 300
echo -e "\n\033[90m[防超时守护] $(date +%H:%M:%S) 终端保持活跃中...\033[0m" >&2
echo -n "按回车键开始换 IP (输入 q 返回)... " >&2
done
) &
KEEP_ALIVE_PID=$!
trap "kill $KEEP_ALIVE_PID 2>/dev/null || true" EXIT
}
start_keep_alive
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 "======================================"
echo "普通实例 IP 刷新模式"
echo "Instance: $INSTANCE_ID"
echo "Region: $REGION"
echo "操作:按回车刷 IP,输入 q 返回实例列表"
echo "======================================"
echo
while true; do
echo -n "按回车键开始换 IP (输入 q 返回)... "
if ! read -r input; then break; fi
if [[ "$input" == "q" ]]; then
echo "正在返回实例列表..."
return 0
fi
while true; do
local CURRENT_ALLOC_ID
CURRENT_ALLOC_ID=$(aws ec2 describe-addresses --filters "Name=instance-id,Values=$INSTANCE_ID" --query "Addresses[0].AllocationId" --output text --region "$REGION")
if [[ "$CURRENT_ALLOC_ID" != "None" ]]; then
local ASSOC_ID
ASSOC_ID=$(aws ec2 describe-addresses --filters "Name=instance-id,Values=$INSTANCE_ID" --query "Addresses[0].AssociationId" --output text --region "$REGION")
echo "清理当前 EIP..."
aws ec2 disassociate-address --association-id "$ASSOC_ID" --region "$REGION"
aws ec2 release-address --allocation-id "$CURRENT_ALLOC_ID" --region "$REGION"
fi
echo "执行 IP 刷新操作..."
local NEW_ALLOC_ID
NEW_ALLOC_ID=$(aws ec2 allocate-address --domain vpc --query "AllocationId" --output text --region "$REGION")
aws ec2 associate-address --instance-id "$INSTANCE_ID" --allocation-id "$NEW_ALLOC_ID" --region "$REGION" >/dev/null
local ASSOC_NEW
ASSOC_NEW=$(aws ec2 describe-addresses --allocation-ids "$NEW_ALLOC_ID" --query "Addresses[0].AssociationId" --output text --region "$REGION")
aws ec2 disassociate-address --association-id "$ASSOC_NEW" --region "$REGION"
aws ec2 release-address --allocation-id "$NEW_ALLOC_ID" --region "$REGION"
local NEW_PUBLIC_IP="None"
while [[ "$NEW_PUBLIC_IP" == "None" ]]; do
sleep 1
NEW_PUBLIC_IP=$(aws ec2 describe-instances --instance-ids "$INSTANCE_ID" --query "Reservations[0].Instances[0].PublicIpAddress" --output text --region "$REGION")
done
echo "获取到 IP: $NEW_PUBLIC_IP"
if grep -Fq "$NEW_PUBLIC_IP" "$USED_IP_FILE"; then
echo ">> IP $NEW_PUBLIC_IP 已使用过,自动重试..."
echo
continue
fi
local TIMESTAMP
TIMESTAMP=$(date "+%Y-%m-%dT%H:%M:%S%z")
echo "$TIMESTAMP $NEW_PUBLIC_IP" >> "$USED_IP_FILE"
echo "✅ 已记录:$TIMESTAMP $NEW_PUBLIC_IP"
echo
break
done
done
}
# ==========================================
# Wavelength 逻辑 (修正版)
# ==========================================
wavelength_swap_interactive() {
local INSTANCE_ID="$1"
local NETWORK_BORDER_GROUP_RAW="$2"
local NETWORK_BORDER_GROUP REGION
NETWORK_BORDER_GROUP="$(normalize_nbg "$NETWORK_BORDER_GROUP_RAW")"
REGION="$(region_from_az "$NETWORK_BORDER_GROUP")"
echo
echo "======================================"
echo "Wavelength 实例循环换运营商 IP 模式"
echo "Instance: $INSTANCE_ID"
echo "Region: $REGION"
echo "操作:按回车刷 IP,输入 q 返回实例列表"
echo "======================================"
echo
while true; do
echo -n "按回车键开始换运营商 IP (输入 q 返回)... "
if ! read -r input; then break; fi
if [[ "$input" == "q" ]]; then
echo "正在返回实例列表..."
return 0
fi
while true; do
local CURRENT_ALLOC_ID
CURRENT_ALLOC_ID=$(aws ec2 describe-addresses --filters "Name=instance-id,Values=$INSTANCE_ID" --query "Addresses[0].AllocationId" --output text --region "$REGION")
if [[ "$CURRENT_ALLOC_ID" != "None" ]]; then
local ASSOC_ID
ASSOC_ID=$(aws ec2 describe-addresses --filters "Name=instance-id,Values=$INSTANCE_ID" --query "Addresses[0].AssociationId" --output text --region "$REGION")
echo "释放旧 IP..."
aws ec2 disassociate-address --association-id "$ASSOC_ID" --region "$REGION"
aws ec2 release-address --allocation-id "$CURRENT_ALLOC_ID" --network-border-group "$NETWORK_BORDER_GROUP" --region "$REGION"
fi
echo "分配新 IP..."
local NEW_ALLOC_ID
NEW_ALLOC_ID=$(aws ec2 allocate-address --domain vpc --network-border-group "$NETWORK_BORDER_GROUP" --query "AllocationId" --output text --region "$REGION")
aws ec2 associate-address --instance-id "$INSTANCE_ID" --allocation-id "$NEW_ALLOC_ID" --region "$REGION" >/dev/null
local ASSOC_NEW
ASSOC_NEW=$(aws ec2 describe-addresses --allocation-ids "$NEW_ALLOC_ID" --query "Addresses[0].AssociationId" --output text --region "$REGION")
echo "解绑并释放临时 IP..."
aws ec2 disassociate-address --association-id "$ASSOC_NEW" --region "$REGION"
aws ec2 release-address --allocation-id "$NEW_ALLOC_ID" --network-border-group "$NETWORK_BORDER_GROUP" --region "$REGION"
local PUBLIC_DNS INSTANCE_IP
PUBLIC_DNS=$(aws ec2 describe-instances --instance-ids "$INSTANCE_ID" --query "Reservations[0].Instances[0].PublicDnsName" --output text --region "$REGION")
if [[ -n "$PUBLIC_DNS" && "$PUBLIC_DNS" != "None" ]]; then
local DNS_HOST IP_PART
DNS_HOST="${PUBLIC_DNS%%.*}"
IP_PART="${DNS_HOST#ec2-}"
# 修复:移除转义符,只替换为纯点号
INSTANCE_IP="${IP_PART//-/.}"
else
INSTANCE_IP=$(aws ec2 describe-instances --instance-ids "$INSTANCE_ID" --query "Reservations[0].Instances[0].PublicIpAddress" --output text --region "$REGION")
fi
echo "当前运营商 IP → $INSTANCE_IP"
if grep -Fq "$INSTANCE_IP" "$USED_IP_FILE"; then
echo ">> IP $INSTANCE_IP 已用过,重试…"
echo
continue
fi
local TIMESTAMP
TIMESTAMP=$(date "+%Y-%m-%dT%H:%M:%S%z")
echo "$TIMESTAMP $INSTANCE_IP" >> "$USED_IP_FILE"
echo "✅ 已记录:$TIMESTAMP $INSTANCE_IP"
echo
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_$$"
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"
if [[ ! -s "$TMP_LIST" ]]; then
echo "❌ 未找到实例(region=$REGION, state=$STATE_FILTER)" >&2
rm -f "$TMP_LIST"
exit 1
fi
local max_idx
max_idx=$(wc -l < "$TMP_LIST" | tr -d ' ')
{
echo
echo "======================================"
echo "自动获取 EC2 实例列表 (Region: $REGION)"
echo "======================================"
printf "%-4s %-20s %-10s %-20s %s\n" "No." "InstanceId" "State" "AZ" "PublicIP"
echo "----------------------------------------------------------------------"
} >&2
local idx=0
local line IID STATE AZ PUB PRI NAME
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" >&2
done < "$TMP_LIST"
echo "----------------------------------------------------------------------" >&2
echo >&2
local choice
while true; do
echo -n "请输入要选择的实例编号(1-$max_idx): " >&2
if ! read -r choice; then exit 1; fi
if [[ "$choice" =~ ^[0-9]+$ ]] && (( choice >= 1 && choice <= max_idx )); then
local picked
picked=$(sed -n "${choice}p" "$TMP_LIST")
read -r IID STATE AZ PUB PRI NAME <<< "$picked"
printf "%s\t%s\n" "$IID" "$AZ"
rm -f "$TMP_LIST"
return 0
fi
echo "无效输入:$choice" >&2
done
}
# ==========================================
# 主程序
# ==========================================
echo -n "实例状态过滤(running/all,默认 running): "
read -r STATE_FILTER
STATE_FILTER="${STATE_FILTER:-running}"
while true; do
SEL="$(select_instance "$REGION" "$STATE_FILTER")"
INSTANCE_ID="$(echo "$SEL" | cut -f1)"
AZ="$(echo "$SEL" | cut -f2)"
echo
echo "✅ 已选择实例:$INSTANCE_ID"
echo " AZ:$AZ"
echo
if is_wavelength_az "$AZ"; then
NBG="$(normalize_nbg "$AZ")"
wavelength_swap_interactive "$INSTANCE_ID" "$NBG"
else
simple_swap_interactive "$INSTANCE_ID" "$REGION"
fi
clear
echo "已返回实例列表,请重新选择..."
done