IPV6地址展开纯Shell脚本

纯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