我是TCL的新手并试图通过做一些简单的脚本来学习,我已经开始编写一个简单的脚本,它从给定的起始IP地址生成有效的IP地址。
我设法写了一个,但遇到了两个问题,
以下是我的代码:
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]
答案 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"
}
如果您使用任一列表创建命令(内联regexp
或split
),您可以检查结果的列表长度:
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] .]
如果您想要多个新地址,请重复此过程。
文档:binary,error,expr,if,incr,join,lassign,{{3 }},llength,lmap,mathop,proc,regexp,scan,set,split
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"