Basically, every variable in Tcl can be considered as a string. The internal representation of such a variable depends on its execution state (see Chapter 2, Advanced Tcl features for more details). Being a dynamic language, Tcl converts the value of a variable to a suitable type at runtime. Let's consider the following example:

We introduce the new Tcl command expr that allows arithmetic operations. In this example, the string "2" is converted to an integer by expr and it returns correct value, but the string text causes an error, because it cannot be converted.

To operate on strings, the string command is extremely useful. The convention of this command is that it accepts a subcommand name as the first argument. For example, if we assume that str is a Hello World! string, in order to get its length, you can use:

Also, in order to reverse it, use:

The String match command can be used to verify if the given string matches the pattern. The pattern is basically another string, but apart from normal characters, some special sequences can be also used:

Allows escaping of any of the special characters, such as ?, *, [ or ] and treats them as normal characters to be match with

Matches exactly one character, only that specified inside [] brackets. It is possible to use the [a-z] syntax to match all characters in this range, in this case from 'a' to 'z

Matches 0 or more of any of the characters

The command returns 1 if the string is successfully matched and it returns 0 otherwise:

Note that in the last two cases, this pattern is put into curly braces to prevent the interpreter from considering the content of the [] braces as a command to be executed.

Describing every string subcommand is beyond the scope of this chapter, the details can be found in the manual at http://www.tcl.tk/man/tcl8.5/TclCmd/string.htm.

It is worth mentioning at this point that depending on the context, the variable's value may be treated as a text string, a number, or a logical value, just as we will see in the following examples.

More than pure strings, Tcl introduces the concept of two interesting data typesâlists and arrays (hashmaps).

Lists

A Tcl list holds an ordered sequence of elements such as strings, other lists, and other variables. To create a list, you can use the list command:

set myList [list element1 element2 element3]

In this example, we create a list using three elements, separated with whitespaces, and assign the list to the myList variable.

What is interesting is that there is no straight demarcation between the list and the string, and therefore, the following code also creates a list:

set myList "element1 element2 element3"

Curly braces are also possible here. The conclusion is that every string may be considered as a list. The advantage of using the list command is that it takes care of correct formatting, adding braces and backslashes when necessary. Consider either of the following two commands:

set myList "one two {another list here}" set myList {one two {another list here}}

They will create a list in which the third element will be another list consisting of elements specified in curly braces. The same effect can be achieved using the list command:

set myList [list one two [list another list here]]

From the user's point of view, a list is both a string and a list, because there is no visible difference:

% puts $myList one two {another list here}

As you can see, the list command added the {} braces at the correct points.

So why even bother with lists? Lists allow you to organize and structure your data, and the benefits of using them are that Tcl offers a set of commands that operates on lists. Lists tend to be extremely useful in typical scripts, therefore, we will briefly review the possible operations on them.

To get the number of elements in your list, use llength . This command takes one parameter, that is, the list. The output is the number of elements in the list.

% llength [list one two three] 3

The command lindex retrieves an element from a list. In basic usage, it takes the list and the index of the element as arguments. Just as in other programming languages, indexes start from zero in Tcl.

% set myList [list one two three]; lindex $myList 0 one

We can also specify the index as the special keyword end , which indicates the last element. For example:

% set myList [list one two three]; lindex $myList end three

We can also specify end-<n> , which indicates the nth element from the end. The value end-0 is the same as the end value; the value end-1 specifies the last but one item and so on. For example:

% set myList [list one two three]; lindex $myList end-1 two

If the retrieved element is another list, lindex also has the ability to retrieve its element by providing additional indexes:

% set myList [list one two [list another list here]]; lindex $myList 2 0 another

In this example, the third element (its index value is 2) of myList is, in turn, another embedded list, and we retrieve its first elementâstring another âby specifying an additional index value to lindex â 0 .

The lrange command returns a subset of the original list. It accepts the list as the first argument followed by the index of the first and last items that should be included in the new list. Both the first and last elements are included in the resulting list. The index can be in the form of an integer or in the end or end-<n> forms.

This command returns a new list consisting of elements that is the start to end indexes:

% set myList {1 2 3 4 5}; puts [lrange $myList 1 3] 2 3 4

In this case, 1 is the starting index and 2 the ending one, so the returned list consists of the second and third elements of the original list.

We can use the lappend command to add one or more elements to an existing list. It accepts the variable name of the list, followed by any number of elements to be added to the list. The command inserts new elements at the end of the specified variable and does not return the new listâinstead, it is automatically set as the new value of the specified variable.

For example:

% set myList {one two}; lappend myList 3 4; puts $myList one two 3 4

This command can also be used to create new lists if the variable passed as the first argument does not exist.

The linsert command can be used to insert one or more elements into a list at the specified index. Unlike lappend , it takes the value of the list as its first argument, followed by the index to insert elements at and any number of elements to insert. For example:

% set myList {1 5}; set myList [linsert $myList 1 2 3 4]; puts $myList 1 2 3 4 5

As for any other list-related command, the index can be in the form of an integer or in the end or end-<n> forms.

The lset command allows you to replace the values of elements with new ones. It accepts a variable name, and one or more indexes, followed by the new value to set. The changed list is stored back in the variable specified as the first argument.

For example, we can specify a single index to modify the nth element of a list:

% set myList {1 2 3}; lset myList 1 0; puts $myList 1 0 3

Similar to lindex , this command can access and modify elements of sub-lists. For example:

% set myList [list one two [list another list here]]; lsert myList end 0 new; puts $mylist One two {new list here}

The lreplace command is also similar to it, but allows replacing a set of elements. It accepts the list as the first argument, the start and end indexes to replace, and the new items to insert instead of the specified elements. The command removes elements between start and end inclusively and inserts new items in their place. Like linsert , it does not alter an existing list, but returns a new one. For example:

% set myList {1 2 3 4 5}; puts [lreplace $myList 1 3 0 0] 1 0 0 5

In case you don't specify new replacement items, the command will only remove elements within a range:

% set myList {1 2 3 4 5}; puts [lreplace $myList 1 3] 1 5

The lsort command can be used to sort lists in Tcl. For example:

% set myList {5 1 3 4 2}; puts [lsort $myList] 1 2 3 4 5

It is also possible to modify the sorting order by specifying additional options, for example -decreasing:

% set myList {5 1 3 4 2}; puts [lsort -decreasing $myList] 5 4 3 2 1

By default, the elements are compared as text valuesâtherefore, when sorting 9, 10, 11, and, 2, the result would be 10, 11, 2, and 9. The following switches allow us to define how elements are compared, which affects the output of the sorting operation:

Switch Description Example -ascii Compares elements as text; this is the default if no switch has been specified List: {a b c 9 10 11 2} Sorted: {2 9 10 11 a b c} -integer Compares elements as integer values List: {9 10 11 2} Sorted: {2 9 10 11} -real Compares elements as floating point values List: {11.0 2.0 9 1.2 1} Sorted: {1 1.2 2.0 9 11.0} -dictionary Compares elements using dictionary-style comparison; see the following paragraph for more details List: { Di beta al Beta} Sorted: {al Beta beta Di}

Dictionary-based sorting is similar to text-based comparison, except that character case is ignored here. An exception is when both the elements are the same, in that case, the uppercase elements are put before lowercase elements. If strings contain numbers, they are compared as integers, not characters. For example, when sorting 9, 10, 11, and 2 using the -dictionary switch, the result would be 2, 9, 10, and 11.

We can also tell lsort to use all elements in the list as sub-lists and compare them only using specified elements of these sub-lists, by using the -index option. The value for the option can be a single index or a list of indexes, where each element of the index list indicates an index in a sub-list. For example:

puts [lsort -integer -index 1 {{Alpha 2} {Beta 1} {Gamma 3}}] {Beta 1} {Alpha 2} {Gamma 3}

Another option is to use your own custom implementation of the comparison algorithm. To do so, you have to define a command that accepts two arguments (items from the list that is being sorted) and returns the integer value by complying to the following rules:

The value is negative when the first argument is considered to be lower than the second

The value is 0 when both arguments are considered equal

The value is a positive value when the first argument is higher than the second one

It is illustrated in the following example:

proc compareStrings {s1 s2} { return [expr {[string length $s1] - [string length $s2]}] } set animals [list snake crocodile monkey cat] puts "default: [lsort $animals]" puts "custom: [lsort -command compareStrings $animals]"

We have the animals list that contains some animal names. The default implementation will sort it in the alphabetical order, but we will be able to define our own implementation in the form of the compareString procedure, which will sort it in the order of the word's length:

default: cat crocodile monkey snake custom: cat snake monkey crocodile

An interesting property of the lsort command is that when two elements are considered as the same, their order in the output is the same as the order in which they were taken as inputs. This can be used to sort lists of lists using multiple orders. We need to sort the list by each element we want it to be sorted using, but in the reverse order.

For example, we can sort a list where each element is a sub-list consisting of first name, last name, and age. If we want to order it by the last name and the decreasing order of age for the same last names, we need to sort it by age first and later by the last name:

set list {{John Doe 32} {Joe Smith 29} {Jane Doe 35}} set list [lsort -integer decreasing -index 2 $list] puts [lsort -dictionary -index 1 $temp]

The result of this sort would be:

{John Doe 32} {Jane Doe 35} {Joe Smith 29}

You can search through your list to find the element(s) that match your pattern using lsearch:

% set myList {zero one two three}; puts [lsearch $myList three] 3

This command looks for the element three in myList , and returns the index of the first occurrence of the searched elementâ3 (or -1 if element is not found). It will be able to accept options such as:

-glob âspecifies that a pattern is in the glob-style (similar to file mask, see the string match command's description for details: http://www.tcl.tk/man/tcl8.5/TclCmd/string.htm)

-regexp âsimilar to the previous option, but the pattern is compliant with the regular expression's syntax (http://www.tcl.tk/man/tcl8.5/TclCmd/re_syntax.htm)

-inline âthe command will return the value of the element found instead of the index

-all âthe command will return a list of all matching occurrences, instead of just the first one

These options can be combined together; for your convenience, here is an example of their usage:

set myList {zero one two three} puts "First match (glob) : \ [lsearch -glob $myList *t*] -> \ [lsearch -inline -glob $myList *t*]" puts "First match (regexp): \ [lsearch -regexp $myList e$] -> \ [lsearch -inline -regexp $myList e$]" puts "All matches (glob) : \ [lsearch -all -glob $myList *t*] -> \ [lsearch -all -inline -glob $myList *t*]" puts "All matches (regexp): \ [lsearch -all -regexp $myList e$] -> \ [lsearch -all -inline -regexp $myList e$]"

The expression *t* will match any string containing the letter t and the regular expression e$ will match any string that ends with the letter e .

The output will be:

First match (glob) : 2 -> two First match (regexp): 1 -> one All matches (glob) : 2 3 -> two three All matches (regexp): 1 3 -> one three

The command lassign allows you to assign values from the list to different variables on a single line. For example:

lassign {1 2 3} a b c

The effect of this command is equal to the execution of the following:

set a 1 set b 2 set c 3

The first argument is a list, and the consecutive arguments are names of the variables. If there are more variables than list elements, the remaining elements will be set to empty strings.