在shell脚本中扩展ipv6地址

问题描述 投票:0回答:8

我想根据给定的 IPv6 地址更新 djbdns (dbndns) 配置文件,例如

2a01:488:66:1000:523:f116:0:1
::1

dbndns 需要扩展的 IPv6 地址,例如

2a010488006610000523f11600000001
2a01:488:66:1000:523:f116:0:1

扩展此类 IPv6 地址的最简单方法是什么?

shell ipv6
8个回答
13
投票

使用

sipcalc
可能会做到这一点。它提供的信息比您需要的更多,但是一点
grep
cut
可以解决这个问题:-)

$ EXPANDED=`sipcalc 2001::1 | fgrep Expanded | cut -d '-' -f 2`
$ echo $EXPAND
2001:0000:0000:0000:0000:0000:0000:0001

作为参考,这是

sipcalc
的完整输出:

$ sipcalc 2001::1
-[ipv6 : 2001::1] - 0

[IPV6 INFO]
Expanded Address        - 2001:0000:0000:0000:0000:0000:0000:0001
Compressed address      - 2001::1
Subnet prefix (masked)  - 2001:0:0:0:0:0:0:1/128
Address ID (masked)     - 0:0:0:0:0:0:0:0/128
Prefix address          - ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff
Prefix length           - 128
Address type            - Aggregatable Global Unicast Addresses
Network range           - 2001:0000:0000:0000:0000:0000:0000:0001 -
                          2001:0000:0000:0000:0000:0000:0000:0001

5
投票

我最近想要一个无依赖的解决方案,可以跨 shell 移植并在 openwrt 等平台上工作。我想出了以下片段:

# helper to convert hex to dec (portable version)
hex2dec(){
    [ "$1" != "" ] && printf "%d" "$(( 0x$1 ))"
}

# expand an ipv6 address
expand_ipv6() {
    ip=$1

    # prepend 0 if we start with :
    echo $ip | grep -qs "^:" && ip="0${ip}"

    # expand ::
    if echo $ip | grep -qs "::"; then
        colons=$(echo $ip | sed 's/[^:]//g')
        missing=$(echo ":::::::::" | sed "s/$colons//")
        expanded=$(echo $missing | sed 's/:/:0/g')
        ip=$(echo $ip | sed "s/::/$expanded/")
    fi

    blocks=$(echo $ip | grep -o "[0-9a-f]\+")
    set $blocks

    printf "%04x:%04x:%04x:%04x:%04x:%04x:%04x:%04x\n" \
        $(hex2dec $1) \
        $(hex2dec $2) \
        $(hex2dec $3) \
        $(hex2dec $4) \
        $(hex2dec $5) \
        $(hex2dec $6) \
        $(hex2dec $7) \
        $(hex2dec $8)
}

我也有这个功能来压缩

# returns a compressed ipv6 address under the form recommended by RFC5952
compress_ipv6() {
    ip=$1

    blocks=$(echo $ip | grep -o "[0-9a-f]\+")
    set $blocks

    # compress leading zeros
    ip=$(printf "%x:%x:%x:%x:%x:%x:%x:%x\n" \
        $(hex2dec $1) \
        $(hex2dec $2) \
        $(hex2dec $3) \
        $(hex2dec $4) \
        $(hex2dec $5) \
        $(hex2dec $6) \
        $(hex2dec $7) \
        $(hex2dec $8)
    )

    # prepend : for easier matching
    ip=:$ip

    # :: must compress the longest chain
    for pattern in :0:0:0:0:0:0:0:0 \
            :0:0:0:0:0:0:0 \
            :0:0:0:0:0:0 \
            :0:0:0:0:0 \
            :0:0:0:0 \
            :0:0; do
        if echo $ip | grep -qs $pattern; then
            ip=$(echo $ip | sed "s/$pattern/::/")
            # if the substitution occured before the end, we have :::
            ip=$(echo $ip | sed 's/:::/::/')
            break # only one substitution
        fi
    done

    # remove prepending : if necessary
    echo $ip | grep -qs "^:[^:]" && ip=$(echo $ip | sed 's/://')

    echo $ip
}

您可以将它们组合起来测试给定的输入是否是 ipv6

# a valid ipv6 is either the expanded form or the compressed one
is_ipv6(){
    expanded="$(expand_ipv6 $1)"
    [ "$1" = "$expanded" ] && return 0
    compressed="$(compress_ipv6 $expanded)"
    [ "$1" = "$compressed" ] && return 0
    return 1
}

我希望这有帮助!这些片段取自https://github.com/chmduquesne/wg-ip。如果您发现任何错误,请贡献!


2
投票
__rfc5952_expand () {
    read addr mask < <(IFS=/; echo $1)
    quads=$(grep -oE "[a-fA-F0-9]{1,4}" <<< ${addr/\/*} | wc -l)
    #[ "${addr:${#addr}-1}" == ":" ] && { addr="${addr}0000"; (( quads++ )); }
    grep -qs ":$" <<< $addr && { addr="${addr}0000"; (( quads++ )); }
    grep -qs "^:" <<< $addr && { addr="0000${addr}"; (( quads++ )); }
    [ $quads -lt 8 ] && addr=${addr/::/:$(for (( i=1; i<=$(( 8 - quads )) ; i++ )); do printf "0000:"; done)}
    #addr=$(
    #for quad in $(IFS=:; echo ${addr}); do
    #    [ "${#quad}" -lt 4 ] && for (( i=${#quad}; i<4 ; i++ )); do quad=0${quad}; done
    #    printf "${delim}${quad}"; delim=":";
    # Or so if you need result without colon, as asked in first post
    #   printf "${quad}";
    #done)
    addr=$(for quad in $(IFS=:; echo ${addr}); do printf "${delim}%04x" "0x${quad}"; delim=":"; done)
    #addr=$(for quad in $(IFS=:; echo ${addr}); do printf "%04x" "0x${quad}"; done)  
    [ ! -z $mask ] && echo $addr/$mask || echo $addr
}

for ip in 2a01:4f8:211:9e::/64 ::1/128; do __rfc5952_expand $ip; done

2a01:04f8:0211:009e:0000:0000:0000:0000/64
0000:0000:0000:0000:0000:0000:0000:0001/128

__rfc5952_compact () {
    read addr mask < <(IFS=/; echo $1)
    addr=$(for quad in $(IFS=:; echo ${addr}); do printf "${delim}%x" "0x${quad}"; delim=":"; done)
    for zeros in $(grep -oE "((^|:)0)+:?" <<< $addr | sort -r | head -1); do addr=${addr/$zeros/::}; done
    [ ! -z $mask ] && echo $addr/$mask || echo $addr
}

for ip in 2a01:04f8:0211:009e:00:0001:0000:0000/64 0000:0000:0000:0000:0000:0000:0000:0001/128; do __rfc5952_compact $ip; done

2a01:4f8:211:9e:0:1::/64
::1/128

1
投票

使用 awk,你可以这样做:

$ echo 2001::1 | awk '{if(NF<8){inner = "0"; for(missing = (8 - NF);missing>0;--missing){inner = inner ":0"}; if($2 == ""){$2 = inner} else if($3 == ""){$3 = inner} else if($4 == ""){$4 = inner} else if($5 == ""){$5 = inner} else if($6 == ""){$6 = inner} else if($7 == ""){$7 = inner}}; print $0}' FS=":" OFS=":" | awk '{for(i=1;i<9;++i){len = length($(i)); if(len < 1){$(i) = "0000"} else if(len < 2){$(i) = "000" $(i)} else if(len < 3){$(i) = "00" $(i)} else if(len < 4){$(i) = "0" $(i)} }; print $0}' FS=":" OFS=":"
$ 2001:0000:0000:0000:0000:0000:0000:0001

第一次调用 awk 在“::”之间添加缺失的零和冒号;第二次调用 awk 将缺失的 0 添加到每个组中。

要修剪冒号,只需将最后一个 OFS=":" 替换为 OFS=""。


0
投票

这适合你吗?

kent$  echo "2a01:488:66:1000:523:f116:0:1"|awk -F: '{for(i=1;i<=NF;i++)x=x""sprintf ("%4s", $i);gsub(/ /,"0",x);print x}'
2a010488006610000523f11600000001

0
投票

POSIX shell 解决方案:

#!/bin/sh
expand_ipv6()
{
  __expand_ipv6_ip="${1%%/*}"
  __expand_ipv6_mask=""

  # extract and filter mask at end of address
  case "$1" in
    */*)
      __expand_ipv6_mask="${1#*/}"
      __expand_ipv6_mask="/${__expand_ipv6_mask%%[^0-9/]*}"
  esac

  case "$__expand_ipv6_ip" in
    :*) __expand_ipv6_ip="0$__expand_ipv6_ip"
  esac

  case "$__expand_ipv6_ip" in
    *::*)
      __expand_ipv6_colons="$(echo "$__expand_ipv6_ip" | tr -c -d ':')"
      __expand_ipv6_expanded="$(echo ":::::::::" | sed -e "s/$__expand_ipv6_colons//" -e 's/:/:0/g')"
      __expand_ipv6_ip="$(echo "$__expand_ipv6_ip" | sed "s/::/$__expand_ipv6_expanded/")"
    ;;
  esac

  __expand_ipv6_blocks="$(echo "$__expand_ipv6_ip" | grep -o '[0-9a-f]\+' | while read -r __expand_ipv6_hex; do [ -n "$__expand_ipv6_hex" ] && printf " %d" "$((0x$__expand_ipv6_hex % 65536))"; done)"
  printf "%04x:%04x:%04x:%04x:%04x:%04x:%04x:%04x" $__expand_ipv6_blocks
  printf "%s\n" "$__expand_ipv6_mask"
}

测试:

> expand_ipv6 ab:12:345f::2/32
00ab:0012:345f:0000:0000:0000:0000:0002/32

这是基于 user48768 的这个答案的稍微改进的版本,他从这个 github 源复制了它,其中:

  • 包括对可选掩码的支持(如果输入没有掩码,则也不会打印)
  • 可以安全地防止代码注入(由于使用双引号)
  • 不会覆盖现有变量(由于
    __expand_ipv6_
    -前缀)

注意:

expand_ipv6
函数需要一个有效的IPv6地址,否则结果也不一定有效。要测试字符串是否是有效的 IPv6 地址,请使用:

#!/bin/sh
is_ipv6()
{
  ip -6 route get "$1" >/dev/null 2>/dev/null || [ $? -ne 1 ]
}

此外,为了压缩 IPv6 地址(expand_ipv6 的相反),这里还有一个改进的解决方案:

#!/bin/sh
compress_ipv6()
{
  __compress_ipv6_ip="$(echo "$1" | sed -e 's/::/:0:/g' | grep -o "[0-9a-f]\+" | while read -r __compress_ipv6_hex; do [ -n "$__compress_ipv6_hex" ] && printf ":%x" "$((0x$__compress_ipv6_hex))"; done)"

  for __compress_ipv6_chain in :0:0:0:0:0:0:0:0 :0:0:0:0:0:0:0 :0:0:0:0:0:0 :0:0:0:0:0 :0:0:0:0 :0:0:0 :0:0 :0
  do
    case "$__compress_ipv6_ip" in
      *$__compress_ipv6_chain*)
        __compress_ipv6_ip="$(echo "$__compress_ipv6_ip" | sed -e "s/$__compress_ipv6_chain/::/" -e 's/:::/::/')"
        break
    esac
  done

  case "$__compress_ipv6_ip" in
    ::*) ;;
    :*) __compress_ipv6_ip="${__compress_ipv6_ip#:}"
  esac
  echo "$__compress_ipv6_ip"
}

0
投票

awk 详细版本:

{
    OFS=":"
    FS=":"
    j=NF;
    for(i=8;i>=1;i--) {
        # If the i field is empty and there are still missing fields (j<i)
        if (!$(i) && j<i) {
            # if j has a value, copy to i and clean j
            if($(j)) {
                $(i)=$(j);
                $(j)="";
                j--
            # if not, fill i it with zero
            } else {
                $(i)=0
            }
        }
        # Now just add the leading 0
        $(i)=gensub(" ","0","g",sprintf("%4s",$(i)))
    }
    print
}

还有紧凑型 oneliner:

awk -v OFS=":" -F: '{j=NF;for(i=8;i>=1;i--) {if (!$(i) && j<i) {if($(j)) {$(i)=$(j);$(j)="";j--} else $(i)=0} $(i)=gensub(" ","0","g",sprintf("%4s",$(i))) } print}'

您可以使用它从输入中读取:

$ echo ::1 | awk -v OFS=":" -F: '{j=NF;for(i=8;i>=1;i--) {if (!$(i) && j<i) {if($(j)) {$(i)=$(j);$(j)="";j--} else $(i)=0} $(i)=gensub(" ","0","g",sprintf("%4s",$(i))) } print}'
0000:0000:0000:0000:0000:0000:0000:0001
$ echo 2001::1 | awk -v OFS=":" -F: '{j=NF;for(i=8;i>=1;i--) {if (!$(i) && j<i) {if($(j)) {$(i)=$(j);$(j)="";j--} else $(i)=0} $(i)=gensub(" ","0","g",sprintf("%4s",$(i))) } print}'
2001:0000:0000:0000:0000:0000:0000:0001
$ echo 2001:1:1:1:1:1:1:1 | awk -v OFS=":" -F: '{j=NF;for(i=8;i>=1;i--) {if (!$(i) && j<i) {if($(j)) {$(i)=$(j);$(j)="";j--} else $(i)=0} $(i)=gensub(" ","0","g",sprintf("%4s",$(i)))
 } print}'
2001:0001:0001:0001:0001:0001:0001:0001
$ echo 2001:1:1::1:1:1:1 | awk -v OFS=":" -F: '{j=NF;for(i=8;i>=1;i--) {if (!$(i) && j<i) {if($(j)) {$(i)=$(j);$(j)="";j--} else $(i)=0} $(i)=gensub(" ","0","g",sprintf("%4s",$(i))) 
} print}'
2001:0001:0001:0000:0001:0001:0001:0001
$ echo 2001:1:1:1:1:1:1:1 | awk -v OFS=":" -F: '{j=NF;for(i=8;i>=1;i--) {if (!$(i) && j<i) {if($(j)) {$(i)=$(j);$(j)="";j--} else $(i)=0} $(i)=gensub(" ","0","g",sprintf("%4s",$(i)))
 } print}'
2001:0001:0001:0001:0001:0001:0001:0001

当它不是有效的 IPv6 地址时,不要指望它能够工作。没有验证作为 IPv6 超过 8 个字段或使用 :: 两次。


0
投票

这是我的简单解决方案,适用于 Bash 和 BusyBox

sh

ip
是输入,
expanded_ip
变量是输出。

# Add enough leading zeros to each field in the address so that each field is
# exactly 4 hexadecimal digits long.
expanded_ip="$(
    printf '%s\n' "$ip" \
    | sed -E 's/([0-9a-f]+)/000\1/g; s/0+([0-9a-f]{4})/\1/g'
)"

# If the address contains `::`, expand it into zeros.
if [[ "$expanded_ip" == *"::"* ]]; then
    # The part of the address before the `::`.
    ip_start="${expanded_ip/::*/}"
    # The part of the address after the `::`.
    ip_end="${expanded_ip/*::/}"

    # Start with an IPv6 address of all zeros.
    zeros="0000:0000:0000:0000:0000:0000:0000:0000"

    # Slice out just the zeros that should replace the `::`.
    zeros="${zeros:"${#ip_start}"}"
    if [[ "${ip_end}" ]]; then
        zeros="${zeros:0:-"${#ip_end}"}"
    fi

    # Join together the part before the `::`, the zeros replacing the `::`,
    # and part after the `::` to get the final expanded IPv6 address.
    expanded_ip="$ip_start$zeros$ip_end"
fi

作为奖励,由于这是我的用例,这里有另一个命令将扩展的 IP 转换为 PTR 记录地址:

# Remove the colons, separate each hexadecimal digit with a `.`, reverse it, and
# append `.ip6.arpa` to get the final PTR record name.
record_name="$(
    printf '%s\n' "$expanded_ip" \
    | sed -E 's/://g; s/(.)/.\1/g; s/^\.//' \
    | rev
).ip6.arpa"
© www.soinside.com 2019 - 2024. All rights reserved.