在我的shell中,我需要检查字符串是否是有效的IPv6地址。
我找到了两种方法,它们对我来说都不够理想。
一个是http://twobit.us/2011/07/validating-ip-addresses/,而我想知道这样一个共同的要求是否一定如此复杂。
另一种是在shell脚本中扩展ipv6地址,这很简单,但对于Linux的主要发行版来说,sipcalc不是一个常见的默认实用程序。
所以我的问题是,有没有一种简单的方法或实用程序可以用shell验证IPv6地址?
提前谢谢。
大多数发行版都预装了包iproute2(名称可能有所不同)。因此,您可以依靠命令ip查询路由表:
ip -6 route get <probe_addr>/128 >/dev/null 2>&1
即使在没有适当路由的机器上,当探测使用有效的v6语法时,也会传递rc=0。
另请参阅grand central的答案,rc==1表示无效的IPv6地址。
第一个链接中的代码并不是特别优雅,但模块化风格的修复,我认为除此之外你无法简化太多(正如评论中所示,它可能已经太简单了)。该规范很复杂,要求提供许多可选功能,这对最终用户来说很好,但对实现者来说很麻烦。
您可能会找到一个通用脚本语言的库,它将此逻辑正确地封装在库中。我会想到Python,实际上Python 3.3包括一个名为ipaddress
的标准模块;对于旧版本,请尝试
#!/usr/bin/env python
import socket
import sys
try:
socket.inet_pton(socket.AF_INET6, sys.argv[1])
result=0
except socket.error:
result=1
sys.exit(result)
另请参阅检查IP地址
这里有一个POSIX兼容shell脚本的解决方案,该脚本使用可选的子网掩码处理IPv4和IPv6地址。要测试应该而不是具有子网掩码的IP,只需在执行测试时向其传递一个伪掩码即可。这看起来像是很多代码,但它应该比使用外部程序(如grep或可能分叉的脚本)快得多
压缩为::的单个IPv6零组将被视为无效。强烈反对使用这种表述,但在技术上是正确的。代码中有一条注释,解释了如果您希望允许这样的地址,如何更改此行为。
#!/bin/sh
set -e
# return nonzero unless $1 contains only digits, leading zeroes not allowed
is_numeric() {
case "$1" in
"" | *[![:digit:]]* | 0[[:digit:]]* ) return 1;;
esac
}
# return nonzero unless $1 contains only hexadecimal digits
is_hex() {
case "$1" in
"" | *[![:xdigit:]]* ) return 1;;
esac
}
# return nonzero unless $1 is a valid IPv4 address with optional trailing subnet mask in the format /<bits>
is_ip4() {
# fail if $1 is not set, move it into a variable so we can mangle it
[ -n "$1" ] || return
IP4_ADDR="$1"
# handle subnet mask for any address containing a /
case "$IP4_ADDR" in
*"/"* ) # set $IP4_GROUP to the number of bits (the characters after the last /)
IP4_GROUP="${IP4_ADDR##*"/"}"
# return failure unless $IP4_GROUP is a positive integer less than or equal to 32
is_numeric "$IP4_GROUP" && [ "$IP4_GROUP" -le 32 ] || return
# remove the subnet mask from the address
IP4_ADDR="${IP4_ADDR%"/$IP4_GROUP"}";;
esac
# backup current $IFS, set $IFS to . as that's what separates digit groups (octets)
IP4_IFS="$IFS"; IFS="."
# initialize count
IP4_COUNT=0
# loop over digit groups
for IP4_GROUP in $IP4_ADDR ;do
# return failure if group is not numeric or if it is greater than 255
! is_numeric "$IP4_GROUP" || [ "$IP4_GROUP" -gt 255 ] && IFS="$IP4_IFS" && return 1
# increment count
IP4_COUNT=$(( IP4_COUNT + 1 ))
# the following line will prevent the loop continuing to run for invalid addresses with many occurrences of .
# this makes no difference to the result, but may improve performance when validating many such invalid strings
[ "$IP4_COUNT" -le 4 ] || break
done
# restore $IFS
IFS="$IP4_IFS"
# return success if there are 4 digit groups, otherwise return failure
[ "$IP4_COUNT" -eq 4 ]
}
# return nonzero unless $1 is a valid IPv6 address with optional trailing subnet mask in the format /<bits>
is_ip6() {
# fail if $1 is not set, move it into a variable so we can mangle it
[ -n "$1" ] || return
IP6_ADDR="$1"
# handle subnet mask for any address containing a /
case "$IP6_ADDR" in
*"/"* ) # set $IP6_GROUP to the number of bits (the characters after the last /)
IP6_GROUP="${IP6_ADDR##*"/"}"
# return failure unless $IP6_GROUP is a positive integer less than or equal to 128
is_numeric "$IP6_GROUP" && [ "$IP6_GROUP" -le 128 ] || return
# remove the subnet mask from the address
IP6_ADDR="${IP6_ADDR%"/$IP6_GROUP"}";;
esac
# perform some preliminary tests and check for the presence of ::
case "$IP6_ADDR" in
# failure cases
# *"::"*"::"* matches multiple occurrences of ::
# *":::"* matches three or more consecutive occurrences of :
# *[^:]":" matches trailing single :
# *"."*":"* matches : after .
*"::"*"::"* | *":::"* | *[^:]":" | *"."*":"* ) return 1;;
*"::"* ) # set flag $IP6_EXPANDED to true, to allow for a variable number of digit groups
IP6_EXPANDED=0
# because :: should not be used for remove a single zero group we start the group count at 1 when :: exists
# NOTE This is a strict interpretation of the standard, applications should not generate such IP addresses but (I think)
# they are in fact technically valid. To allow addresses with single zero groups replaced by :: set $IP6_COUNT to
# zero after this case statement instead
IP6_COUNT=1;;
* ) # set flag $IP6_EXPANDED to false, to forbid a variable number of digit groups
IP6_EXPANDED=""
# initialize count
IP6_COUNT=0;;
esac
# backup current $IFS, set $IFS to : to delimit digit groups
IP6_IFS="$IFS"; IFS=":"
# loop over digit groups
for IP6_GROUP in $IP6_ADDR ;do
# if this is an empty group then increment count and process next group
[ -z "$IP6_GROUP" ] && IP6_COUNT=$(( IP6_COUNT + 1 )) && continue
# handle dotted quad notation groups
case "$IP6_GROUP" in
*"."* ) # return failure if group is not a valid IPv4 address
# NOTE a subnet mask is added to the group to ensure we are matching addresses only, not ranges
! is_ip4 "$IP6_GROUP/1" && IFS="$IP6_IFS" && return 1
# a dotted quad refers to 32 bits, the same as two 16 bit digit groups, so we increment the count by 2
IP6_COUNT=$(( IP6_COUNT + 2 ))
# we can stop processing groups now as we can be certain this is the last group, : after . was caught as a failure case earlier
break;;
esac
# if there are more than 4 characters or any character is not a hex digit then return failure
[ "${#IP6_GROUP}" -gt 4 ] || ! is_hex "$IP6_GROUP" && IFS="$IP6_IFS" && return 1
# increment count
IP6_COUNT=$(( IP6_COUNT + 1 ))
# the following line will prevent the loop continuing to run for invalid addresses with many occurrences of a single :
# this makes no difference to the result, but may improve performance when validating many such invalid strings
[ "$IP6_COUNT" -le 8 ] || break
done
# restore $IFS
IFS="$IP6_IFS"
# if this address contained a :: and it has less than or equal to 8 groups then return success
[ "$IP6_EXPANDED" = "0" ] && [ "$IP6_COUNT" -le 8 ] && return
# if this address contained exactly 8 groups then return success, otherwise return failure
[ "$IP6_COUNT" -eq 8 ]
}
以下是一些测试。
# tests
TEST_PASSES=0
TEST_FAILURES=0
for TEST_IP in 0.0.0.0 255.255.255.255 1.2.3.4/1 1.2.3.4/32 12.12.12.12 123.123.123.123 101.201.201.109 ;do
! is_ip4 "$TEST_IP" && printf "IP4 test failed, test case '%s' returned invalidn" "$TEST_IP" && TEST_FAILURES=$(( TEST_FAILURES + 1 )) || TEST_PASSES=$(( TEST_PASSES + 1 ))
done
for TEST_IP in ::1 ::1/128 ::1/0 ::1234 ::bad ::12 1:2:3:4:5:6:7:8 1234:5678:90ab:cdef:1234:5678:90ab:cdef
1234:5678:90ab:cdef:1234:5678:90ab:cdef/127 1234:5678:90ab::5678:90ab:cdef/64 f:1234:c:ba:240::1
1:2:3:4:5:6:1.2.3.4 ::1.2.3.4 ::1.2.3.4/0 ::ffff:1.2.3.4 ;do
! is_ip6 "$TEST_IP" && printf "IP6 test failed, test case '%s' returned invalidn" "$TEST_IP" && TEST_FAILURES=$(( TEST_FAILURES + 1 )) || TEST_PASSES=$(( TEST_PASSES + 1 ))
done
for TEST_IP in junk . / 0 -1.0.0.0 1.2.c.0 a.0.0.0 " 1.2.3.4" "1.2.3.4 " " " 01.0.0.0 09.0.0.0 0.0.0.01
0.0.0.09 0.09.0.0.0 0.01.0.0 0.0.01.0 0.0.0.a 0.0.0 .0.0.0.0 256.0.0.0 0.0.0.256 "" 0 1 12
123 1.2.3.4/s 1.2.3.4/33 1.2.3.4/1/1 ;do
is_ip4 "$TEST_IP" && printf "IP4 test failed, test case '%s' returned validn" "$TEST_IP" && TEST_FAILURES=$(( TEST_FAILURES + 1 )) || TEST_PASSES=$(( TEST_PASSES + 1 ))
done
for TEST_IP in junk "" : / :1 ::1/ ::1/1/1 :::1 ::1/129 ::12345 ::bog ::1234:345.234.0.0 ::sdf.d ::1g2
1:2:3:44444:5:6:7:8 1:2:3:4:5:6:7 1:2:3:4:5:6:7:8/1c1 1234:5678:90ab:cdef:1234:5678:90ab:cdef:1234/64
1234:5678:90ab:cdef:1234:5678::cdef/64 ::1.2.3.4:1 1.2.3.4:: ::1.2.3.4j ::1.2.3.4/ ::1.2.3.4:junk ::1.2.3.4.junk ;do
is_ip6 "$TEST_IP" && printf "IP6 test failed, test case '%s' returned validn" "$TEST_IP" && TEST_FAILURES=$(( TEST_FAILURES + 1 )) || TEST_PASSES=$(( TEST_PASSES + 1 ))
done
printf "test complete, %s passes and %s failuresn" "$TEST_PASSES" "$TEST_FAILURES"
valid_ip(){
ip -6 route get "$1"/128 >/dev/null 2>&1
case "$?" in
0|2) return 0
1) return 1
esac
}
我接受了sgundlach的答案,但在一台没有ip6连接的机器上需要它,所以通过测试和阅读手册页,我发现我可以相信退出代码1表示语法无效,同时0表示成功,2表示有效语法,但报告了内核错误。
来自手册页:
Exit status is 0 if command was successful, and 1 if there is a syntax error. If an error was reported by the kernel exit status is 2.