纯Shell实现IPv6地址展开(反向格式化),将压缩格式转为8段4位完整格式。支持验证地址有效性、处理带前缀的IPv6地址。
目录导航
纯SHELL脚本
#!/bin/bash
set -euo pipefail
# 脚本名称
SCRIPT_NAME=$(basename "$0")
# 帮助信息函数
show_help() {
cat << EOF
用法: $SCRIPT_NAME [选项] <IPv6地址>
功能: 纯Shell实现IPv6地址展开(反向格式化),将压缩格式转为8段4位完整格式
支持验证地址有效性、处理带前缀的IPv6地址
选项:
-h, --help 显示此帮助信息并退出
-p, --prefix 保留IPv6地址的前缀(如2001:db8::1/64),默认仅展开地址部分
示例:
$SCRIPT_NAME 2001:db8::1
$SCRIPT_NAME -p ::1/128
$SCRIPT_NAME fe80::1234:5678:9abc:def0
EOF
}
# 验证IPv6地址字符合法性(基础校验)
validate_ipv6_chars() {
local addr="$1"
# IPv6合法字符:0-9, a-f, A-F, :, /(前缀)
if [[ ! "$addr" =~ ^[0-9a-fA-F:/]+$ ]]; then
echo "错误: IPv6地址包含非法字符(仅允许0-9/a-f/A-F/:/)" >&2
exit 1
fi
}
# 核心:展开IPv6地址为8段4位完整格式
expand_ipv6_core() {
local pure_addr="$1"
local expanded_segments=()
# 步骤1:检查::出现的次数(合法IPv6只能有一个::)
local colon_count=$(grep -o "::" <<< "$pure_addr" | wc -l)
if [[ $colon_count -gt 1 ]]; then
echo "错误: 无效的IPv6地址(::只能出现一次)" >&2
exit 1
fi
# 步骤2:处理全零压缩(::)
if [[ "$pure_addr" == "::" ]]; then
echo "0000:0000:0000:0000:0000:0000:0000:0000"
return
fi
# 步骤3:替换::为临时标记,拆分地址段
local temp_addr="${pure_addr//::/:__ZERO__:}"
# 正确拆分地址段(IFS=: 生效)
local segments=()
IFS=: read -ra segments <<< "$temp_addr"
# 过滤空段(处理开头/结尾的::)
local filtered_segments=()
for seg in "${segments[@]}"; do
if [[ -n "$seg" ]]; then
filtered_segments+=("$seg")
fi
done
segments=("${filtered_segments[@]}")
# 步骤4:计算需要补充的0段数(总段数需为8)
local zero_pos=-1
local seg_count=${#segments[@]}
for i in "${!segments[@]}"; do
if [[ "${segments[$i]}" == "__ZERO__" ]]; then
zero_pos=$i
break
fi
done
# 步骤5:构建展开后的段列表(先填充原始段,再补充零段)
local new_segments=()
if [[ $zero_pos -ne -1 ]]; then
# 计算需要填充的0段数
local fill_zeros=$((8 - (seg_count - 1)))
if [[ $fill_zeros -lt 0 ]]; then
echo "错误: IPv6地址段数过多(超过8段)" >&2
exit 1
fi
# 拼接前半段
for ((i=0; i<zero_pos; i++)); do
new_segments+=("${segments[$i]}")
done
# 填充0段(先填空字符串,后续补零)
for ((i=0; i<fill_zeros; i++)); do
new_segments+=("")
done
# 拼接后半段
for ((i=zero_pos+1; i<seg_count; i++)); do
new_segments+=("${segments[$i]}")
done
else
# 无::的情况,直接使用原段(需正好8段)
new_segments=("${segments[@]}")
if [[ ${#new_segments[@]} -ne 8 ]]; then
echo "错误: IPv6地址段数错误(无::时需正好8段)" >&2
exit 1
fi
fi
# 步骤6:验证每段并补零到4位(核心展开逻辑)
for seg in "${new_segments[@]}"; do
# 空段/零段处理为0000
if [[ -z "$seg" || "$seg" == "0" ]]; then
expanded_segments+=("0000")
continue
fi
# 验证段长度(最多4位)
if [[ ${#seg} -gt 4 ]]; then
echo "错误: IPv6地址段 '$seg' 过长(最多4位)" >&2
exit 1
fi
# 验证是合法16进制数
if ! [[ "$seg" =~ ^[0-9a-fA-F]{1,4}$ ]]; then
echo "错误: IPv6地址段 '$seg' 包含非法字符" >&2
exit 1
fi
# 补零到4位(用bc工具确保16进制补零,兼容大小写)
# 先转大写→转10进制→转16进制→补前导零到4位→转小写(可选,保持输入大小写也可)
local seg_upper=$(echo "$seg" | tr 'a-f' 'A-F')
local seg_10=$(echo "ibase=16; $seg_upper" | bc)
local seg_4digit=$(printf "%04x" "$seg_10" | tr 'A-F' 'a-f')
expanded_segments+=("$seg_4digit")
done
# 步骤7:拼接为8段完整地址
local expanded_addr=$(IFS=:; echo "${expanded_segments[*]}")
echo "$expanded_addr"
}
# 主函数:处理前缀+调用核心展开逻辑
expand_ipv6() {
local addr="$1"
local keep_prefix="$2"
local pure_addr
local prefix=""
# 分离地址和前缀
if [[ "$addr" =~ / ]]; then
pure_addr="${addr%/*}"
prefix="${addr#*/}"
# 验证前缀合法性(0-128的整数)
if ! [[ "$prefix" =~ ^[0-9]+$ ]] || [[ "$prefix" -lt 0 ]] || [[ "$prefix" -gt 128 ]]; then
echo "错误: 无效的IPv6前缀 '$prefix'(需为0-128的整数)" >&2
exit 1
fi
else
pure_addr="$addr"
fi
# 基础字符验证
validate_ipv6_chars "$pure_addr"
# 调用核心展开逻辑
local expanded_addr=$(expand_ipv6_core "$pure_addr")
# 拼接前缀(如果需要)
if [[ "$keep_prefix" -eq 1 && -n "$prefix" ]]; then
echo "${expanded_addr}/${prefix}"
else
echo "$expanded_addr"
fi
}
# 解析命令行参数
KEEP_PREFIX=0
IPV6_ADDR=""
while [[ $# -gt 0 ]]; do
case "$1" in
-h|--help)
show_help
exit 0
;;
-p|--prefix)
KEEP_PREFIX=1
shift
;;
*)
if [[ -z "$IPV6_ADDR" ]]; then
IPV6_ADDR="$1"
shift
else
echo "错误: 多余的参数 '$1'" >&2
show_help >&2
exit 1
fi
;;
esac
done
# 检查是否输入了IPv6地址
if [[ -z "$IPV6_ADDR" ]]; then
echo "错误: 必须指定IPv6地址" >&2
show_help >&2
exit 1
fi
# 执行展开并输出结果
expand_ipv6 "$IPV6_ADDR" "$KEEP_PREFIX"
运行样例
[gbase@vm151 ~]$ ./ipv6_expand.sh -p 2001:1::5:0:0:8/64
2001:0001:0000:0000:0005:0000:0000:0008/64
[gbase@vm151 ~]$ ./ipv6_expand.sh 2001:1::5:0:0:8
2001:0001:0000:0000:0005:0000:0000:0008