用于生成N个有效IP地址的脚本?

时间:2014-09-07 16:11:52

标签: tcl

我是TCL的新手并试图通过做一些简单的脚本来学习,我已经开始编写一个简单的脚本,它从给定的起始IP地址生成有效的IP地址。

我设法写了一个,但遇到了两个问题,

  1. 最后一个八位字节在数字192.168.1.025之前添加了零
  2. 当我指定像250.250.5.1这样的起始IP时,它无法生成正确的ips,
  3. 以下是我的代码:

    proc generate {start_addr total_addr} {
        if {$total_addr == 0} {return}
        regexp {([0-9]+\.)([0-9]+\.)([0-9]+\.)([0-9]+)} $start_addr match a b c d
        set filename "output.txt"
        set fileId [open $filename "a"]
        puts $fileId $a$b$c$d
        close $fileId
        while {$a<255 && $b <255 && $c <255 && $d < 255 } {
            set d [expr {$d + 1}];
            set filename "output.txt"
            set fileId [open $filename "a"]
            puts $fileId $a$b$c$d
            close $fileId
            set total_addr [expr {$total_addr - 1}];
            if {$total_addr == 1} {return}
            if {$total_addr > 1 && $d == 255} {
                set c [expr {$c + 1}];
                set d 1
                set filename "output.txt"
                set fileId [open $filename "a"]
                puts $fileId $a$b$c$d
                close $fileId
                set total_addr [expr {$total_addr - 1}];
            }
            if {$total_addr > 1 && $c==255 && $d == 255} {
                set b [expr {$b + 1}];
                set c 1
                set d 1
                set filename "output.txt"
                set fileId [open $filename "a"]
                puts $fileId $a$b$c$d
                close $fileId
                set total_addr [expr {$total_addr - 1}];
            }
            if {$total_addr > 1 && $b == 255 && $c == 255 && $d == 255} {
                set a [expr {$a + 1}];
                set b 1
                set c 1
                set d 1
                set filename "output.txt"
                set fileId [open $filename "a"]
                puts $fileId $a$b$c$d
                close $fileId
                set total_addr [expr {$total_addr - 1}];
            }
        }
    }
    
    
    flush stdout
    puts "Please enter the starting IPv4 address with . as delimiter EX: 1.1.1.1"
    set start_addr [gets stdin]
    regexp {([0-9]+\.)([0-9]+\.)([0-9]+\.)([0-9]+)} $start_addr match a b c d
    if {$a <= 255 & $b <= 255 & $c <= 255 & $d <= 255} {
        puts "this is a valid ip address"
    } else {
        puts "this not a valid ip address"
    }
    flush stdout
    puts "Please enter the total number of IPv4 address EX: 1000"
    set total_addr [gets stdin]
    set result [generate $start_addr $total_addr]
    

3 个答案:

答案 0 :(得分:0)

要使用简单方式解析IP地址,最好使用scan。如果您了解C sscanf()函数,则Tcl的scan非常相似(特别是%d匹配十进制数)。像这样,我们可以这样做:

if {[scan $start_addr "%d.%d.%d.%d" a b c d] != 4} {
    error "some components of address are missing"
}

当出现问题时抛出错误是个好主意。你可以稍后catch或者让脚本退出,具体取决于你的选择。 (您仍然需要检查数字范围。)

更一般地说,Tcllib中的一个包进行IP地址解析。它比你可能需要的要完整得多,但它就在那里。

你应该做的第二件大事?将代码附加到文件中的字符串。它可以是一个简短的程序,足够短,显然正确

proc addAddress {filename address} {
    set fileId [open $filename "a"]
    puts $fileId $address
    close $fileId
}

然后你可以替换:

        set filename "output.txt"
        set fileId [open $filename "a"]
        puts $fileId $a$b$c$d
        close $fileId

使用:

       addAddress "output.txt" $a$b$c$d

少出错。减少噪音。 (Protip:在那里考虑$a.$b.$c.$d。)

更严重的是,您的代码实际上不太可行。这太复杂了。特别是,每次循环都应该生成一个地址,并且应该专注于如何正确推进计数器。强烈建议使用incr将1添加到整数。

您可以尝试这样的事情:

incr d
if {$d > 255} {
    set d 1
    incr c
}
if {$c > 255} {
    set c 1
    incr b
}
if {$b > 255} {
    set b 1
    incr a
}
if {$a > 255} {
    set a 1
}

但那效率不高。我们可以做得更好:

if {[incr d] > 255} {
    set d 1
    if {[incr c] > 255} {
        set c 1
        if {[incr b] > 255} {
            set b 1
            if {[incr a] > 255} {
                set a 1
            }
        }
    }
}

更好(尽管实际的有效IP地址范围更广:中间可以有0或两个,例如127.0.0.1 ...)

答案 1 :(得分:0)

拆分地址

除了在Tcllib中使用ip包之外,有几种方法可以分割IPv4“点小数”地址并将八位字节值放入四个变量中。你使用的那个是

regexp {([0-9]+\.)([0-9]+\.)([0-9]+\.)([0-9]+)} $start_addr match a b c d

这基本上有效,但有一些问题。第一个问题是地址1.234.1.234将被分为1. 234. 1. 234,然后当您尝试使用incr命令时在前三个变量上,您将收到一条错误消息(我想这就是您使用expr {$x + 1}而不是incr)的原因。相反,写

regexp {(\d+)\.(\d+)\.(\d+)\.(\d+)} $start_addr match a b c d

此表达式将点放在捕获括号外,并将整数值放入变量中。使用简写\d十进制数)代替[0-9]集也是一个好主意。但你也可以这样做:

regexp -all -inline -- {\d+} $start_addr

您只需要regexp收集所有(-all)不间断的十进制数字序列,并将其作为列表返回(-inline)。由于您将结果作为列表获得,因此您需要将lassign list assign )转换为变量:

lassign [regexp -all -inline -- {\d+} $start_addr] a b c d

但是,如果你能在没有正则表达式的情况下做到,你应该这样做。 Donal建议

scan $start_addr "%d.%d.%d.%d" a b c d

哪个好。另一种方法是split点上的字符串:

lassign [split $start_addr .] a b c d

(再次获得一个列表作为结果,需要在第二步中将其分配给您的变量。)

检查结果

正如Donal所写,无论何时从用户输入(以及许多其他情况)创建数据,检查您是否确实获得了预期的结果,这是一个好主意。如果使用赋值regexp,则命令返回1或0,具体取决于匹配是成功还是失败。此结果可以直接插入if调用:

if {![regexp {(\d+)\.(\d+)\.(\d+)\.(\d+)} $start_addr match a b c d]} {
    error "input data didn't match IPv4 dot-decimal notation"
}

Donal已经举了一个检查scan结果的例子。在这种情况下,您将检查4,因为该命令返回它管理的成功匹配的数量。

if {[scan $start_addr "%d.%d.%d.%d" a b c d] != 4} {
    error "input data didn't match IPv4 dot-decimal notation"
}

如果您使用任一列表创建命令(内联regexpsplit),您可以检查结果的列表长度:

if {[llength [set result [split $start_addr .]]] == 4} {
    lassign $result a b c d
} else {
    error "input data didn't match IPv4 dot-decimal notation"
}

此检查之后应检查所有变量的八位字节值(0-255)。一种方便的方法是这样的:

proc isoctet args {
    ::tcl::mathop::* {*}[lmap octet $args {expr {0 <= $octet && $octet <= 255}}]
}

(将测试作为函数分解通常是一个好主意;如果您在代码中的多个位置使用测试,那么它实际上就是法律*。)

此命令isoctet将许多值作为参数,将它们作为特殊参数args中的列表集中在一起。 lmap命令创建一个与原始列表具有相同元素数的新列表,其中每个元素的值是将给定脚本应用于原始列表中相应元素的结果。在这种情况下,lmap会生成一个1和0的列表,具体取决于值是否为真八位字节值。例如:

input list:  1 234 567 89
result list: 1   1   0  1

然后,结果列表将{*}扩展为::tcl::mathop::*命令的各个参数,这些参数将它们相乘。为什么?因为如果1和0可以作为真值和假值,则1和0列表的乘积恰好与同一列表的逻辑连词(AND,&amp;&amp;)完全相同。

result 1: 1 1 0 1
product : 0 (false)
result 2: 1 1 1 1
product : 1 (true)

所以,

if {![isoctet $a $b $c $d]} {
    error "one of the values was outside the (0, 255) range"
}

生成新地址

生成新地址的最不性感方式可能是在Tcl中使用现成的工具:binary

binary scan [binary format c* [list $a $b $c $d]] I n

此调用首先将整数值列表(将它们约束为八位字节大小)转换为位字符串,然后将该位字符串解释为big-endian 32位整数(如果您的机器使用小端整数,您应该使用转化说明符i代替I)。

增加数量。 Wheee!

incr n

将其转换回8位值列表:

binary scan [binary format I $n] c4 parts

parts的组件现在是签名的 8位整数,即最高值为127,而应该高于127的值现在为负值。将值转换为无符号(0 - 255)值,如下所示:

lassign [lmap part $parts {expr {$part & 0xff}}] a b c d

并将它们连接到点小数字符串,如下所示:

set addr [join [list $a $b $c $d] .]

如果您想要多个新地址,请重复此过程。

文档:binaryerrorexprifincrjoinlassign,{{3 }},llengthlmapmathopprocregexpscansetsplit

lmap是一个Tcl 8.6命令。针对Tcl 8.4和8.5的纯Tcl实现{*}

*)如果有任何法律。您必须学习的是,这些规则与Matrix的规则没有什么不同。其中一些可以弯曲。其他人可能会被打破。

答案 2 :(得分:-1)

proc ip_add { ip add } {
    set re "^\\s*(\\d+)\.(\\d+)\.(\\d+)\.(\\d+)\\s*$"
    if [regexp $re $ip match a b c d] {
        set x [expr {(($a*256+$b)*256+$c)*256+$d+$add}]
        set d [expr {int(fmod($x,256))}]
        set x [expr {int($x/256)}]
        set c [expr {int(fmod($x,256))}]
        set x [expr {int($x/256)}]
        set b [expr {int(fmod($x,256))}]
        set x [expr {int($x/256)}]
        set a [expr {int(fmod($x,256))}]
        return "$a.$b.$c.$d"
    } else {
        puts stderr "invalid ip $ip"
        exit 1
    }
}

set res [ip_add "127.0.0.1" 512]
puts "res=$res"