TCL regsub uses RegEx match as index in associate array - tcl

I'd like to automatically convert URLs, i.e
"https://sc-uat.ct.example.com/sc/" into "https://invbeta.example.com/sc/"
"https://sc-dev.ct.example.com/sc/" into "https://invtest.example.com/sc/"
"https://sc-qa.ct.example.com/sc/" into "https://invdemo.example.com/sc/"
I've tried following code snippet in TCL
set loc "https://sc-uat.ct.example.com/sc/"
set envs(dev) "test"
set envs(uat) "beta"
set envs(qa) "demo"
puts $envs(uat)
regsub -nocase {://.+-(.+).ct.example.com} $loc {://inv[$envs(\1)].example.com} hostname
puts "new location = $hostname"
But the result is: new location = https://inv[$envs(uat)].example.com/sc/
It seems that [$envs(uat)] is NOT evaluated and substituted further with the real value. Any hints will be appreciated. Thanks in advance

But the result is: new location =
https://inv[$envs(uat)].example.com/sc/ It seems that [$envs(uat)] is eval-ed further.
You meant to say: [$envs(uat)] is not evaluated further?
This is because due to the curly braces in {://inv[$envs(\1)].example.com}, the drop-in string is taken literally, and not subjected to variable or command substitution. Besides, you don't want command and variable substitution ([$envs(\1)]), just one of them: $envs(\1) or [set envs(\1)].
To overcome this, you must treat the regsub-processed string further via subst:
set hostname [subst -nocommands -nobackslashes [regsub -nocase {://.+-(.+).ct.example.com} $loc {://inv$envs(\1).example.com}]]
Suggestions for improvement
I advise to avoid the use of subst in this context, because even when restricted, you might run into conflicts with characters special to Tcl in your hostnames (e.g., brackets in the IPv6 authority parts). Either you have to sanitize the loc string before, or, better work on string ranges like so:
if {[regexp -indices {://(.+-(.+)).ct.example.com} $loc _ replaceRange keyRange]} {
set key [string range $loc {*}$keyRange]
set sub [string cat "inv" $envs($key)]
set hostname [string replace $loc {*}$replaceRange $sub]
}

Related

How to remove a single letter/number

I have single letters and numbers in a variable that I would like to remove
example inputs:
USA-2019-1-aoiwer
USA-A-jowerasf
BB-a_owierlasdf-2019
flsfwer_5_2015-asfdlwer
desired outputs:
USA-2019--aoiwer
USA--jowerasf
BB-_owierlasdf-2019
flsfwer__2015-asfdlwer
my code:
bind pub "-|-" !aa proc:aa
proc proc:aa { nick host handle channel arg } {
set line [lindex $arg 0]
set line [string map {[a-z] """} $line]
set line [string map {[0-9] """} $line]
putnow "PRIVMSG $channel :$line"
}
Unfortunately that does not work and i have no other idea
Regards
string map would remove all the lowercase letters and numbers, if it worked. However, you also have unbalanced quotes, which causes a syntax error when the proc is resolving.
I would recommend using regsub. The hard part, however, would be to get a proper expression to do the task. I will suggest the following:
bind pub "-|-" !aa proc:aa
proc proc:aa { nick host handle channel arg } {
set line [lindex $arg 0]
regsub -nocase -all {([^a-z0-9]|\y)[a-z0-9]([^a-z0-9]|\y)} $line {\1\2} line
putnow "PRIVMSG $channel :$line"
}
Basically ([^a-z0-9]|\y) matches a character that is non alphanumeric, or a word boundary (which will match at the beginning of a sentence for example if it can, or at the end of a sentence), and stores it (this is the purpose of the parens).
The matched groups are stored in order starting with 1, so in the replace portion of regsub, I'm placing the parts that shouldn't be replaced back where they were.
The above should work fine.
You could technically go a little fancier with a slightly different expression:
regsub -nocase -all {([^a-z0-9]|\y)[a-z0-9](?![a-z0-9])} $line {\1} line
Which uses a negative lookahead ((?! ... )).
Anyway, if you do want to get more in depth, I recommend reading the manual on regular expression syntax

Extracting query string value

How to extract the username value from this query string (HTTP url-encoded): username=james&password=pwd in Tcl?
I can get it through Java's request.getParameter("username"); but how to get using Tcl?
The first stage is to split the query string up, and form a dictionary of it (which isn't strictly correct, but I'm guessing you don't care about the case where someone puts multiple username fields in the query string!). However, you also need to decode the encoding of the contents, and that's pretty awful:
proc QueryStringToDict {qs} {
set mapping {}
foreach item [split $qs "&"] {
if {[regexp {^([^=]+)=(.*)$} $item -> key value]} {
dict set mapping [DecodeURL $key] [DecodeURL $value]
}
}
return $mapping
}
proc DecodeURL {string} {
# This *is* tricky! The URL encoding of fields is way nastier than you thought!
set mapped [string map {+ { } \[ "\\\[" \] "\\\]" $ "\\$" \\ "\\\\"} $string]
encoding convertfrom utf-8 \
[subst [regsub -all {%([[:xdigit:]]{2})} $string {[format %c 0x\1]}]]
}
set qs "username=james&password=pwd"
set info [QueryStringToDict $qs]
puts "user name is [dict get $info username]"
In 8.7 (currently in alpha) it'll be much simpler to do that inner encoding; there won't need to be that subst call in there for example. But you haven't got that version of Tcl; nobody has (except for people who insist on being right on the bleeding edge and get themselves into trouble over it).
Assuming this is a CGI environment, where the environment will contain
REQUEST_METHOD=GET
QUERY_STRING='username=james&password=pwd'
or
REQUEST_METHOD=POST
CONTENT_LENGTH=27
# and stdin contains "username=james&password=pwd"
then use tcllib's ncgi module
$ cat > cgi.tcl
#!/usr/bin/env tclsh
package require ncgi
::ncgi::parse
array set params [::ncgi::nvlist]
parray params
$ printf "username=james&password=pwd" | env REQUEST_METHOD=POST CONTENT_LENGTH=27 ./cgi.tcl
params(password) = pwd
params(username) = james
$ env REQUEST_METHOD=GET QUERY_STRING='username=james&password=pwd' ./cgi.tcl
params(password) = pwd
params(username) = james
An alternative to Donal's suggestion, sharing the spirit, but building on battery pieces: tcllib rest package:
(1) To process the query (as part of a valid URL)
% package req rest
1.3.1
% set query [rest::parameters ?username=jo%3Dhn]; # http:// is default scheme, ? is minimum URL boilerplate
username jo%3Dhn
(2) Run a URL decoder (e.g., the one by Donal or the one from Rosetta code):
% proc urlDecode {str} {
set specialMap {"[" "%5B" "]" "%5D"}
set seqRE {%([0-9a-fA-F]{2})}
set replacement {[format "%c" [scan "\1" "%2x"]]}
set modStr [regsub -all $seqRE [string map $specialMap $str] $replacement]
return [encoding convertfrom utf-8 [subst -nobackslash -novariable $modStr]]
}
then:
% set info [lmap v $query {urlDecode $v}]
username jo=hn
% dict get $info username
jo=hn

Looking for a search string in a file and storing in a variable - TCL

I'm trying to read a file with two variables, username and password, such as:
user1,password1
How can I separate the values and store in different variables?
I have tried this but it seems not to store the string itself.
It seems that it can't see the comma in there.
Another idea was to use \M to match the "," after every string and store that string but it does not work.
Appreciate some help.
set varuser [lsearch -inline -all $userpass "*,"]
set varuser [regexp {,\s+"(.*)"} $userpass all value]
Since a user's password may contain a comma, I would favour a regex approach:
% set line {user1,my,clever,password}
user1,my,clever,password
% lassign [regexp -inline {^(.+?),(.*)$} $line] -> user pass
% set user
user1
% set pass
my,clever,password
Assuming neither as commas (and ignoring the possible need to trim spaces) [split] is simpler than a regexp and may be enough for the task, along with [lassign]:
tcl> set line {user1,password1}
user1,password1
tcl> lassign [split $line ,] user pwd
tcl> puts $user
user1
tcl> puts $pwd
password1

Remove prefix substring from string

I have a string abc.def.ghi.j and I want to remove abc. from that, so that I have def.ghi.j.
1) What would be the best approach to remove such a prefix which has a specific pattern?
2) Since in this case, abc is coincidentally the prefix, that probably makes things easier. What if we wanted abc.ghi.j as the output?
I tried it with the split method like this
set name abc.def.ghi.j
set splitVar [split $name {{abc.}} ]
The problem is that it splits across each of a, b, c and . seperately instead of as a whole.
Well, there's a few ways, but the main ones are using string replace, regsub, string map, or split-lreplace-join.
We probably ought to be a bit careful because we must first check if the prefix really is a prefix. Fortunately, string equal has a -length operation that makes that easy:
if {[string equal -length [string length $prefix] $prefix $string]} {
# Do the replacement
}
Personally, I'd probably use regsub but then I'm happy with using RE engine tricks.
Using string replace
set string [string replace $string 0 [string length $prefix]-1]
# Older versions require this instead:
# set string [string replace $string 0 [expr {[string length $prefix]-1}]]
Using regsub
# ***= is magical and says "rest of RE is simple plain text, no escapes"
regsub ***=$prefix $string "" string
Using string map
# Requires cunning to anchor to the front; \uffff is unlikely in any real string
set string [string map [list \uffff$prefix ""] \uffff$string]
Using split…join
This is about what you were trying to do. It depends on the . being a sort of separator.
set string [join [lrange [split $string "."] 1 end] "."]

TCL command - string trim

I was using the command 'string trimright' to trim my string but I found that this command trims more than required.
My expression is "dssss.dcsss" If I use string trim command to trim the last few characters ".dcsss", it trims the entire string. How can I deal with this?
Command:
set a [string trimright "dcssss.dcsss" ".dcsss"]
puts $a
Intended output:
dcsss
Actual output
""
The string trimright command treats its (optional) last argument as a set of characters to remove (and so .dcsss is the same as sdc. to it), just like string trim and string trimleft do; indeed, string trim is just like using both string trimright and string trimleft in succession. This makes it unsuitable for what you are trying to do; to remove a suffix if it is present, you can use several techniques:
# It looks like we're stripping a filename extension...
puts [file rootname "dcssss.dcsss"]
# Can use a regular expression if we're careful...
puts [regsub {\.dcsss$} "dcssss.dcsss" {}]
# Do everything by hand...
set str "dcssss.dcsss"
if {[string match "*.dcsss" $str]} {
set str [string range $str 0 end-6]
}
puts $str
If what you're doing really is filename manipulation, like it looks like, do use the first of these options. The file command has some really useful commands for working with filenames in a cross-platform manner in it.