This article is archived, the information has been updated or reformatted into the Getting Started with iRules series in the Control Structures & Operators article.

In a previous article, I discussed the if command which is core to almost every iRule.  For those cases where your need to perform conditional testing on a single value, there is another conditional command that can be used that in most cases is faster and easier to read than its corresponding if counterpart.  This article will discuss the "switch" statement, the cases with which you could use it, and some insights into how it is used. Other articles in the series: 


SYNTAX

The syntax for the switch command is as follows:

switch ?options? string pattern body ?pattern body ...?
switch ?options? string {pattern body ?pattern body ...?}

The switch command matches its string argument against each of the pattern arguments in order. As soon as it finds a pattern that matches string it evaluates the following body argument by passing it recursively to the Tcl interpreter and returns the result of that evaluation. If the last pattern argument is default then it matches anything. If no pattern argument matches string and no default is given, then the switch command returns an empty string.

 

If the initial arguments to switch start with - then they are treated as options. The following options are currently supported:

 

-exact
Use exact matching when comparing string to a pattern. This is the default.
-glob
When matching string to the patterns, use glob-style matching (i.e. the same as implemented by the string match command).
-regexp
When matching string to the patterns, use regular expression matching (as described in the re_syntax reference page).
- -
Marks the end of options. The argument following this one will be treated as string even if it starts with a -.

Two syntaxes are provided for the pattern and body arguments. The first uses a separate argument for each of the patterns and commands; this form is convenient if substitutions are desired on some of the patterns or commands. The second form places all of the patterns and commands together into a single argument; the argument must have proper list structure, with the elements of the list being the patterns and commands. The second form makes it easy to construct multi-line switch commands, since the braces around the whole list make it unnecessary to include a backslash at the end of each line. Since the pattern arguments are in braces in the second form, no command or variable substitutions are performed on them; this makes the behavior of the second form different than the first form in some cases.

"switch" as a replacement for "if"

As you can see from the above syntax, the switch statements performs a comparison on a single value indicated by the "string" argument.  So, it is perfect for a replacement to if commands that performs "and", as well as, "or" logic on a single string value.  You'll see examples like this in iRules all the time

if { [HTTP::uri] equals "/foo" } {
  # do something...
} elseif { ([HTTP::uri] equals "/bar") || ([HTTP::uri] equals "/foobar") } {
  # do something else...
} else {
  # don't do anything...
}

You'll see in this example, the bodies of code are executed (in this example just comments) if a conditional test is passed on the value of the HTTP::uri string. This is a perfect candidate for a switch statement.  The following switch example is equivalent to the above if/elseif block.

switch [HTTP::uri] {
  "/foo" {
    # do something...
  }
  "/bar" -
  "/foobar" {
    # do something else...
  }
  default {
    # don't do anything...
  }
}

The one thing that sticks out is the dash ('-') after the "/bar" string comparison.  This tells the TCL interpretor to do a logical OR with the following line.  So basically it's the same as comparing "/bar" OR "/foobar".  You may not see a high degree of readability improvement when comparing the two options, but it becomes much more apparent when there are many more than 2 or 3 tests.  There are hidden benefits though: Generally, switch commands are faster than if statements due to additional expression evaluations that need to occur with if commands.  Since the switch statements only works on a single comparison value, internal optimizations are able to be made in the evaluation process.  This likely isn't going to be a big difference for one or two comparisons, but it is measurable when more are made.

Different ways to use switch

Comparing "exact" matches.

The switch command as several different comparison modes you can use.  Running switch with the "-exact" argument will perform exact string for string comparisons, similar to how the "equals" operator works on two strings.  There are no wild cards or regular expressions, but it can be highly optimized by not using those features.  The above example was an example of the "-exact" option (-exact is the default if no arguments are specified).

Do you really need a regular expression?

We see this alot in iRules

if { [HTTP::uri] matches_regex "/foo*" } {
  # do something 
}

There is an obvious problem with the above iRule: using a regular expression comparison is overkill.  Regular expressions are very CPU intensive and should only be used when there are no other options.  At a minimum, the matches_regex above should be replaced with "starts_with", or "ends_with" or "contains" in situations where end or middle conditions need to be performed.  Hidden inside the "string" TCL command is the "string match" subcommand.  This performs a "poor man's" regular expression allowing for wildcards and character ranges.  For you Unix heads out there, this is very similar to filename globbing in Unix shells.  The usage for "string match" is as follows:

string match ?-nocase? pattern string
*
Matches any sequence of characters in string, including a null string.
?
Matches any single character in string.
[chars]
Matches any character in the set given by chars. If a sequence of the form x-y appears in chars, then any character between x and y, inclusive, will match. When used with -nocase, the end points of the range are converted to lower case first. Whereas {[A-z]} matches '_' when matching case-sensitively ('_' falls between the 'Z' and 'a'), with -nocase this is considered like {[A-Za-z]} (and probably what was meant in the first place).
\x
Matches the single character x. This provides a way of avoiding the special interpretation of the characters *?[]\ in pattern.

How does this relate to the switch command?  Well, lucky for us, the "-glob" argument allows for comparisons based on the TCL string match comparison format.  In almost all cases, one completely remove regular expressions with carefully placed wildcards and character ranges. 

switch -glob [HTTP::uri] {
  "/foo*" {
    # this will match on any string that starts with "/foo"
  }
  "*bar" {
    # this will match on any string that ends with "bar"
  }
  "/foobar[a-zA-Z]" {
    # This will match with any string that starts with "/foobar" followed by any case character from a-z.
  }
  "/foo?bar" {
    # this will match any string that starts with "/foo", followed by any character, and followed by and ending with "bar".
  }
}

As you see, the "globbing" option in the switch statement is very powerful and likely covers most comparison cases. 

For those cases where match comparsions won't cut it

For those situations where you need a bit more indepth checking, the "-regexp" argument is available.  It is beyond the scope of this article to describe regular expressions in great detail.  If you so desire, you can view the documentation on "re_syntax" (http://www.tcl.tk/man/tcl8.4/TclCmd/re_syntax.htm).  Good luck!!!

Since we don't recommend the use of regular expresions, I'm going to omit one from this article.  The format is much the same as the -glob option, but replace the comparison strings with your desired regular expression.

Things that don't work easily with switch commands

Multiple comparison sources

If you are trying to compare multiple values in your if/else command like the following

if { ([HTTP::host] equals "www.foo.com") and ([HTTP::uri] starts_with "/foo") } {
  # do something
}


to use a switch statement, you will have to do something like the following:

switch [HTTP::host] {
  "www.foo.com" {
    switch -glob [HTTP::uri] {
      "/foo*" {
        # do something
      }
    }
  }
}

Logical AND Comparisons

The ability to chain conditionals together with a dash ('-') makes it very easy to do "if (a or b) do c" type test, but it doesn't work for "if (a and b) do c)".  To build a logical AND comparison, you'll have to either to an embedded switch or if within the body of the switch condition.  The above example illustrates this.

Simple "boolean" type tests

If commands are often used to test not only string comparisons, but other boolean tests such as the length of a string with the "string length" command, or numeric comparisons such as "greater than".  There is no way use these types of tests within a switch statement.  We'll, actually you could, but you would have to first perform the test and then put the return value into a string format and then feed that string into a switch.  For these types of conditionals, you are better off sticking with using "if".

Conclusion

If you find your iRule being filled with line after line of if/elseif/elsef/elseif/... commands, then think about switching to making use of the higher performing "switch" command.  It will not only make your iRules run faster (for string comparisons), but in most cases, it is easier to read and maintain.

Get the Flash Player to see this player.