i want to convert string to number to use in SCP command. can someone please help?
set ip "192.168.1.2"
scp:root#$ip//etc/dev/
it doesn't replace $ip with the IP i had set.
how do I convert the IP to number as the scp command is expecting number rather than string.
Thanks
You'll want a library to do that. tcllib has an IP address manipulation module:
package require ip
set ip "192.168.1.2"
set num [ip::toInteger $ip]
puts $num
puts [ip::intToString $num]
-1062731518
192.168.1.2
This looks odd: scp:root#$ip//etc/dev/
Recall that Tcl evaluates commands like command word ..., and there's no whitespace in that "scp" command, so Tcl will try to find a command named "scp:root#192.168.1.2//etc/dev/" -- I bet it can't find one.
This is not about converting any string to a number, but rather an IP address. In Tcl most everything is a string, so you just use the expr command for math and it will do the conversion as necessary. Notice how in my implementation below inside expr I simply refer to [lindex $octets $i] which is a string derived from splitting the IP address, yet the command still runs as expected.
The below is a naive implementation of what you are really asking for: converting an IP address to a number (integer); you would want to enhance it with various validations (length of the segments array, min/max of each segment, etc.) -- this is why a library as suggested in the other answer may be a better way, not to mention possibly being equipped to handle ipv6 which I simply ignore ;)
Here's an explanation of the implementation in general; please anybody feel free to edit with a better one. A note on my implementation: by reversing the list, I make it trivial to multiply the last octet by 256^0, the next to last octet by 256^1, etc, the power being the lindex.
set ip "192.168.1.2"
set ip_as_int 0
set octets [lreverse [split $ip .]]
for {set i 0} {$i < 4} {incr i} {
set ip_as_int [expr {256 ** $i * [lindex $octets $i] + $ip_as_int}]
}
puts $ip_as_int ;#3232235778
Related
In TCL, I need to split an ipv6 address and port combination in the format [fec1::10]:80 to fec1::10 and 80.
Please suggest a way to do it.
Thanks!
(In the examples below I assume that the address will be subjected to further processing (expansion, etc) because there are a lot of forms that it can take: hence, in this preliminary stage I treat it simply as a string of any character rather than groups of hex digits separated by colons. The ip package mentioned by kostix is excellent for processing the address, just not for separating the address from the port number.)
Given the variable
set addrport {[fec1::10]:80}
There are several possible ways, including brute-force regular expression matching:
regexp -- {\[(.+)\]:(\d+)} $addrport -> addr port
(which means "capture a non-empty sequence of any character that is inside literal brackets, then skip a colon and thereafter capture a non-empty sequence of any digit"; the three variables at the end of the invocation get the whole match, the first captured submatch, and the second captured submatch, respectively)
(note 1: American usage of the word 'brackets' here: for British speakers I mean square brackets, not round brackets/parentheses)
(note 2: I'm using the code fragment -> in two ways: as a variable name in the above example, and as a commenting symbol denoting return value in some of the following examples. I hope you're not confused by it. Both usages are kind of a convention and are seen a lot in Tcl examples.)
regexp -inline -- {\[(.+)\]:(\d+)} $addrport
# -> {[fec1::10]:80} fec1::10 80
will instead give you a list with three elements (again, the whole match, the address, and the port).
Many programmers will stop looking for possible solutions here, but you're still with me, aren't you? Because there are more, possibly better, methods.
Another alternative is to convert the string to a two-element list (where the first element is the address and the second the port number):
split [string map {[ {} ]: { }} $addrport]
# -> fec1::10 80
(which means "replace any left brackets with empty strings (i.e. remove them) and any substrings that consist of a right bracket and a colon with a single space; then split the resulting string into a list")
it can be used to assign to variables like so:
lassign [split [string map {[ {} ]: { }} $addrport]] addr port
(which performs a sequential assign from the resulting list into two variables).
The scan command will also work:
scan $addrport {[%[^]]]:%d} addr port
(which means "after a left bracket, take a sequence of characters that does not include a right bracket, then skip a right bracket and a colon and then take a decimal number")
want the result as a list instead?
scan $addrport {[%[^]]]:%d}
# -> fec1::10 80
Even split works, in a slightly roundabout way:
set list [split $addrport {[]:}]
# -> {} fec1 {} 10 {} 80
set addr [lindex $list 1]::[lindex $list 3]
set port [lindex $list 5]
(note: this will have to be rewritten for addresses that are expanded to more than two groups).
Take your pick, but remember to be wary of regular expressions. Quicker, easier, more seductive they are, but always bite you in the ass in the end, they will.
(Note: the 'Hoodiecrow' mentioned in the comments is me, I used that nick earlier. Also note that at the time this question appeared I was still sceptical towards the ip module: today I swear by it. One is never to old to learn, hopefully.)
The ip package from the Tcl standard library can do that, and more.
One of the simplest ways to parse these sorts of things is with scan. It's the command that many Tclers forget!
set toParse {[fec1::10]:80}
scan $toParse {[%[a-f0-9:]]:%d} ip port
puts "host is $ip and port is $port"
The trick is that you want “scan charcters from limited set”. And in production code you want to check the result of scan, which should be the number of groups matched (2 in this case).
Considering the following code:
puts "What show command would you like to execute?"
set cmd [gets stdin]
proc makeLC {str} {
puts "begin"
puts $str
set lStr [string tolower $str]
set lStr [string trim $lStr]
puts "after low and trim"
puts $lStr
set lenStr [string length $lStr]
for {set i 0} {$i < $lenStr} {incr i} {
puts [string index $lStr $i]
}
return $lStr
}
set lcmd [makeLC $cmd]
When a user types "test12345" then backspaces to display "test123" then adds "67" to finally display "test12367"
puts $lStr returns "test12367"
but the "for" loop will display "test12345 67" the spaces between "12345" and "67" I believe are "\b\b".
Why the inconsistancy?
and how do I ensure that when passing $lStr that "test12367" is assigned and "test12345 67" is not
Normally, Tcl programs on Unix are run in a terminal in “cooked” mode. Cooked mode terminals handle all the line editing for you; you can simply just read the finished lines as they are produced. It's very easy to work with cooked mode.
But you can also put the terminal into raw mode, where (typically) the application decides to handle all the key strokes directly itself. (It's usual to turn off echoing of characters at the same time so applications handle the output side as well as the input side.) This is what editors like vi and emacs do, and so does the readline library (used in many programs, like bash). Raw mode is a lot more fiddly to work with, but gives you much more control. Separately from this is whether what is typed is echoed so it can be seen; for example, there's also non-echoing cooked mode, which is useful for passwords.
In your case, it sounds very much like the terminal is in echoing raw mode (unusual!) and your application expects it to be in echoing cooked mode; you're getting the actual character sent from the keyboard when the delete key is pressed (or maybe the backspace key; there's a lot of complexity down there!) which is highly unusual. To restore sanity, do:
# No raw, Yes echo
exec stty -raw echo <#stdin >#stdout
There's something conceptually similar on Windows, but it works through totally different system calls.
Consider using tclreadline which wraps GNU readline providing full support for interactive command-line editing.
Another solution which relies on the presence of an external tool is to wrap the call to the Tcl shell in rlwrap:
rlwrap tclsh /path/to/script/file.tcl
Well, not sure what to do in this regard. A little while ago I modified a logging script for an eggdrop bot.. but now an issue unfolds that for some reason, it is logging actions/text in separate files because of an issue of character case. #channel.html exists, as does #Channel.html, though the former is written to because of the current state of the channel name(it can change if all users leave and one rejoins with different case).
I've narrowed this problem down to what I believe is the issue. file exists 'filename_here'. I've looked through tcl's documentation, and I've read through the wiki regarding mixed case file names(it treats them as different files of course), but I have yet to find such an option(or user made proc) that would allow me to disable this behavior.
Is there a way around/to do this?
It really depends on the file system (i.e., the OS) as file exists is just a thin wrapper around the OS's basic file existence test. Classic Unix filesystems are mostly case-sensitive, whereas Windows filesystems are usually case-insensitive. This means that it is usually best to write your code to be careful with handling the case of things; you probably ought to consider using string tolower to get a channel name in an expected case (since I think IRC channel names are case-insensitive).
But if you can't do that, the best you can do is to get the list of filenames that match case-insensitively and check if that's a single value. Alas, this is a messy operation as glob doesn't have a -nocase option (it's rare that people want such a thing), so we need to use string match -nocase to help out:
set files [lmap f [glob *.html] {
expr {[string match -nocase ${channel}.html $f] ? $f : [continue]}
}]
if {[llength $files] == 1} {
set channel_file [lindex $files 0]
} else {
# Oh no! Ambiguity!
}
That uses lmap from Tcl 8.6; earlier versions of Tcl should use this instead:
set files {}
foreach f [glob *.html] {
if {[string match -nocase ${channel}.html $f]} {
lappend files $f
}
}
if {[llength $files] == 1} {
set channel_file [lindex $files 0]
} else {
# Oh no! Ambiguity!
}
Pick a filename case (#channel.html, #Channel.html or #CHANNEL.HTML) and use string tolower, string totitle or string toupper respectively on filename_here. Then use that value for all file operations.
An lsearch filter on glob can be used to perform a case-insensitive search for a particular file name, e.g.
% lsearch -nocase -all -inline -glob [glob ./*] {*/myfile.txt}
./myFile.txt ./Myfile.txt ./MYFILE.txt
A sanity check using llength on the lsearch result above can be used to flag an error in case more than one file name is returned.
x is a list of device names (device-1, device-2, device-3)
There is a variable created for each device1 by concatenating the string port so you end up with $device-1port.
looping over x creates
[expr $${x}port-2000 ] #x is device-1 so it is trying $device-1port-2000 which throws error.
I would like to get the numeric value of $device-1port into a variable without a dash.
set xvar $${x}port
[expr $xvar-2000 ]
or can i wrap the $${x}port in something within the expr statement.
To read a variable with interpolations in its name, use single-argument set:
set withoutadash [set device-${x}port]
Generally, it's better to use arrays for this kind of thing.
One of the nicest ways to work with such complex variables is to use the upvar command to make a local “nice” alias to the variable. In particular, upvar 0 makes a local alias to a local variable; slightly tricky, but a known technique.
upvar 0 ${x}port current_port
Now, we have any read, write or unset of current_port is the same as a read/write/unset of the port with the awkward name, and you can write your code simply:
puts [expr { $current_port - 2000 }]
set current_port 12345
# etc.
The alias will be forgotten at the end of the current procedure.
Of course, you probably ought to consider using arrays instead. They're just simpler and you don't need to work hard with computed variable names:
set x 1
set device($x,port) 12345
puts [expr {$device($x,port) - 2000}]
I have a list which contains
72.xx.xxx.xxx 72.xx.xxx.xxx
(There are some spaces )
I have a variable say var
Now I have to execute a command for $var times and I have to use "72.xx.xxx.xxx" each time.
let us say the command is :
"reset vpn $IP".
I have to execute the above command say two times (stored in variable) and for two IP address stored in list.
Can any one help with TCL code?
Thanks in advance.
This is really a homework. And the task specification lacks some important details.
If you want to iterate over all IPs in the list, just do
set ips {72.xx.xxx.xxx 72.xx.xxx.xxx}
foreach ip $ips {
reset vpn $ip
}
If you add more values to the ips list, the foreach command will iterate more times, over all the remaining elements.
If you want to iterate over, say, first N elemnts, just pick them one by one:
set ips {72.xx.xxx.xxx 72.xx.xxx.xxx 72.xx.xxx.xxx 72.xx.xxx.xxx ...}
set ntimes 2
for {set ix 0} {$ix < $ntimes} {incr ix} {
reset vpn [lindex $ips $ix]
}
And so on.
No rocket science. Just read the tutorial, read the book and please try to refrain from such simple questions before you actually tried yourself first.