Tasos
Tasos

Reputation: 2046

TCL/TK script issue with string match inside if-statement

I have a script in bash that calls a TCL script for each element on my network which performs some actions based on the type of the element. This is part of the code that checks whether or not the hostname contains a specific pattern(e.g. *CGN01) and then gives the appropriate command to that machine.

if {[string match "{*CGN01}" $hostname] || $hostname == "AthMet1BG01"} {
    expect {
    "*#" {send "admin show inventory\r"; send "exit\r"; exp_continue}
    eof
    }
}

With the code i quoted above i get no error BUT when the hostname is "PhiMSC1CGN01" then the code inside the if is not executed which means that the expression is not correct.

I have tried everything (use of "()" or "{}" or"[]" inside the if) but when i dont put "" on the pattern i get an error like:

invalid bareword "string"
in expression "(string match {*DR0* *1TS0* *...";
should be "$string" or "{string}" or "string(...)" or ...
(parsing expression "(string match {*DR0* *...")
invoked from within
"if {$hostname == "AthMar1BG03" || [string match *CGN01 $hostname]...

or this:

expected boolean value but got "[string match -nocase "*CGN01" $hostname]==0"
while executing
"if {$hostname == "AthMar1BG03" || {[string match -nocase "*CGN01" $hostname]==0}...

when i tried to use ==0 or ==1 on the expression. My TCL-Version is 8.3 and i cant update it because the machine has no internet connecticity :(

Please help me i am trying to fix this for over a month...

Upvotes: 0

Views: 6970

Answers (2)

Peter Lewerin
Peter Lewerin

Reputation: 13282

If you want to match a string that is either exactly AthMet1BG01 or any string that ends with CGN01, you should use

if {[string match *CGN01 $hostname] || $hostname == "AthMet1BG01"} {

(For Tcl 8.5 or later, use eq instead of ==.)

Some comments on your attempts:

(The notes about the expression language used by if go for expr and while as well. It is fully described in the documentation for expr.)

To invoke a command inside the condition and substitute its result, it needs to be enclosed in brackets ([ ]). Parentheses (( )) can be used to set the priority of subexpressions within the condition, but don't indicate a command substitution.

Normally, inside the condition strings need to be enclosed in double quotes or braces ({ }). This is because the expression language that is used to express the condition needs to distinguish between e.g. numbers and strings, which Tcl in general doesn't. Inside a command substitution within a condition, you don't need to use quotes or braces, as long as there are no characters in the string that you need to quote.

The string {abc} contains the characters abc. The string "{abc}" contains the characters {abc}, because the double quotes make the braces normal characters (the reverse also holds). [string match "{*bar}" $str] matches the string {foobar} (with the braces as part of the text), but not foobar.

If you put braces around a command substitution, {[incr foo]}, it becomes just the string [incr foo], i.e. the command isn't invoked and no substitution is made. If you use {[incr foo]==1} you get the string [incr foo]==1. The correct way to write this within an expression is [incr foo]==1, with optional whitespace around the ==.

All this is kind of hard to grok, but when you have it is really easy to use. Tcl is stubborn as a mule about interpreting strings, but carries heavy loads if you treat her right.

ETA an alternate matcher (see comments)

You can write your own alternate string matcher:

proc altmatch {patterns string} {
    foreach pattern $patterns {
        if {[string match $pattern $string]} {
            return 1
        }
    }
    return 0
}

If any of the patterns match, you get 1; if none of the patterns match, you get 0.

% altmatch {*bar f?o} foobar
1
% altmatch {*bar f?o} fao
1
% altmatch {*bar f?o} foa
0

For those who have a modern Tcl version, you can actually add it to the string ensemble so it works like other string commands. Put it in the right namespace:

proc ::tcl::string::altmatch {patterns string} {
    ... as before ...

and install it like this:

% set map [namespace ensemble configure string -map]
% dict set map altmatch ::tcl::string::altmatch
% namespace ensemble configure string -map $map

Documentation: expr, string, Summary of Tcl language syntax

Upvotes: 2

Donal Fellows
Donal Fellows

Reputation: 137777

This command:

if {[string match "{*CGN01}" $hostname] || $hostname == "AthMet1BG01"} {

is syntactically valid but I really don't think that you want to use that pattern with string match. I'd guess that you really want:

if {[string match "*CGN01" $hostname] || $hostname == "AthMet1BG01"} {

The {braces} inside that pattern are not actually meaningful (string match only does a subset of the full capabilities of a glob match) so with your erroneous pattern you're actually trying to match a { at the start of $hostname, any number of characters, and then CGN01} at the end of $hostname. With the literal braces. Simply removing the braces lets PhiMSC1CGN01 match.

Upvotes: 2

Related Questions