Question

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!

Was it helpful?

Solution

(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.)

OTHER TIPS

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).

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top