# Tcl's "..." corresponds to Perl's "...", while the other # quoting construct {...} is more similar to Perl's q{...} # operator. # It's not necessary to quote text data in Tcl as long as # it doesn't contain whitespace. set string {\n} ;# two characters, \ and n set string "jon 'maddog' orwant" ;# literal single quotes set string \n ;# a "newline" character set string "jon \"crosby\" orwant" ;# literal double quotes set string {jon "stills" orwant} ;# literal double quotes set string "jon {nash} orwant" ;# literal braces set string {jon {young} orwant} ;# literal braces set a { this is a multiline string terminated by an unescaped and {unnested} right brace (\}) } |
set value [string range $string $first $last] set value [string range $string $first [expr {$first+$count-1}]] set value [string range $string $first end] set string [string replace $string $first $last $newstring] set string [string replace $string $first [expr {$first+$count-1}] $newstring] set string [string replace $string $first end $newtail] # get a 5-byte string, skip 3, then grab 2 8-byte strings, then the rest binary scan $data "A5 x3 A8 A8 A*" leading s1 s2 trailing # Important note: the above was all well and good when the Cookbook was # written and a character and a byte were the same size. They still # are for some programming languages, but Tcl for one uses 16-bit # Unicode characters to encode strings. # The above unpack/scan works for strings containing only character # codes in the range 0--255, but distorts other strings by truncating # all codes to 8 bits. # To avoid this, the input string can be converted to an 8-bit # encoding before scanning: encoding convertto utf-8 "H\u2082O is the chemical formula for water" # => Hâ??O is the chemical formula for water # split at five-byte boundaries (16-bit safe) set fivers [list] set temp [encoding convertto utf-8 $string] while {[binary scan $temp a5a* group tail]} { lappend fivers $group set temp $tail } if {[string length $tail]} { lappend fivers $tail } set fivers # split at five-char boundaries (16-bit safe) set fivers [regexp -all -inline {.{1,5}} $data] # chop string into individual characters: set chars [split $data {}] # "This is what you have" # +012345678901234567890 Indexing forwards (left to right) # 098765432109876543210- Indexing from end (right to left) # note that 0 means 10 or 20, etc. above # end is a special value that is available in list and string # commands. It is defined as the index of the last element (in # lists), or character (in strings). # Likewise, end-1 is defined as the element/character # preceding the last, and so on. set first [string index "This is what you have" 0] # => T set start [string range "This is what you have" 5 6] # => is set rest [string range "This is what you have" 13 end] # => you have set last [string index "This is what you have" end] # => e set end [string range "This is what you have" end-3 end] # => have set piece [string range "This is what you have" end-7 end-5] # => you # The general technique here is to mutate a string and then assign # it back to the variable. One can [replace] a segment of the string # with another string or with an empty string (deleting the segment) # or simply select a segment using [range]. set string [string replace "This is what you have" 5 6 wasn't] # => This wasn't what you have set string [string replace "This wasn't what you have" end-11 end ondrous] # => This wasn't wondrous set string [string range "This wasn't wondrous" 1 end] # => his wasn't wondrous set string [string range "his wasn't wondrous" 0 end-10] # => his wasn' if {[regexp $pattern [string range $string end-9 end]]} { return "Pattern matches in last 10 characters" } else { return "Match failed" } # substitute "at" for "is", restricted to first five characters regsub -all is [string range $string 0 4] at newstring set string [string replace $string 0 4 $newstring] regsub {(.)(.*)(.)} "make a hat" {\3\2\1} a puts $a # => take a ham set b [string range "To be or not to be" 6 11] # => or not set a "To be or not to be" set b [string range $a 6 7] append b : [string range $a 3 4] # => or:be proc cut2fmt {args} { set positions $args set template {} set lastpos 1 foreach {place} $positions { append template "A[expr {$place-$lastpos}] " set lastpos $place } append template A* return $template } set fmt [cut2fmt 8 14 20 26 30] # => A7 A6 A6 A6 A4 A* |
# In Tcl, commands such as if or while require the value of the # condition expression to be a proper boolean value. If the # value is numeric, 0 is false and anything else is true. For # non-numeric strings, "true", "on", or "yes" is true and # "false", "off", or "no" is false. Any other value for the # condition expression raises an error. # The `boolean operators' return either "1" or "0". # use $b if b has characters, else $c if {[string length $b]} { set a $b } else { set a $c } # use $b if b is non-zero, else $c if {$b != 0} { set a $b } else { set a $c } # set x to $y if $x has no characters if {![string length $x]} { set x $y } # set x to $y if $x is zero if {$x == 0} { set x $y } # set a to $b if b exists, else to $c if {[info exists b]} { set a $b } else { set a $c } # Perl: $dir = shift(@ARGV) || "/tmp"; set arg [lindex $argv 0] set argv [lrange $argv 1 end] if {[string length $arg]} { set dir $arg } else { set dir /tmp } # Perl: $dir = $ARGV[0] || "/tmp"; set arg [lindex $argv 0] if {[string length $arg]} { set dir $arg } else { set dir /tmp } # Perl: $dir = defined($ARGV[0]) ? shift(@ARGV) : "/tmp"; if {[info exists argv] && [llength $argv]} { set dir [lindex $argv 0] set argv [lrange $argv 1 end] } else { set dir /tmp } # Perl: $dir = @ARGV ? $ARGV[0] : "/tmp"; if {[llength $argv]} { set dir [lindex $argv 0] } else { set dir /tmp } # Perl: $count{ $shell || "/bin/sh" }++; if {![string length $shell]} { set shell /bin/sh } if {[info exist count($shell)]} { incr count($shell) } else { set count($shell) 1 } # The catch command intercepts errors raised. In this # case catch is used as an alternative idiom to # [info exists var]. # The pros and cons of the different idioms is # discussed at <URL: http://mini.net/tcl/1322.html>. # find the user name on Unix systems; needs extension to # check getlogin() and getpwuid() if {![catch {string length $env(USER)}]} { set user $env(USER) } elseif {![catch {string length $env(LOGIN)}]} { set user $env(LOGIN) } else { set user "Unknown user" } # The most obvious way to do the above in Tcl is set ::tcl_platform(user) if {![string length $startingPoint]} { set startingPoint Greenwich } # if x has no elements, assign $y to it if {[llength $x] == 0} { set x $y } # if y has elements, assign it to x, otherwise assign $z to x if {[llength $y]} { set x $y } else { set x $z } |
# cross-assignment foreach {b a} $args break # cross-assignment with temp set temp $a set a $b set b $temp unset temp foreach {alpha beta production} [list January March August] break # move beta to alpha, # move production to beta, # move alpha to production foreach {alpha beta production} [list $beta $production $alpha] break |
set num [scan $char %c] set char [format %c $num] format "Number %d is character %c" 101 101 # => Number 101 is character e set utf8data [encoding convertto utf-8 $string] binary scan $utf8data c* codelist set utf8data [binary format c* $codelist] set string [encoding convertfrom utf-8 $utf8data] proc hal2ibm {} { set hal HAL binary scan $hal c* codes foreach {num} $codes { lappend newcodes [incr num] } set ibm [binary format c* $newcodes] } hal2ibm # => IBM |
set a [split $string {}] set utf8data [encoding convertto utf-8 $string] binary scan $utf8data c* a # with -line, . never matches newline foreach 1 [regexp -inline -all -line . $string] { # do something with $1 } proc indChars-1 {s} { array set seen [list] set string $s foreach {char} [split $string {}] { if {[info exists seen($char)]} { incr seen($char) } else { set seen($char) 1 } } puts "unique chars are: {[join [lsort [array names seen]] {}]}" } indChars-1 "an apple a day" # => unique chars are: { adelnpy} # Of course, if all you care about are which unique # characters appear, it's much easier: proc uniqueChars-1 {s} { puts "unique chars are: {[join [lsort -unique [split $s {}]] {}]}" } uniqueChars-1 "an apple a day" # => unique chars are: { adelnpy} # simplistic checksum calculation proc simpleChecksum {string} { set sum 0 binary scan $string c* codes foreach {code} $codes { incr sum $code } return $sum } simpleChecksum "an apple a day" # => 1248 # The Trf package, which is available at # <URL: http://www.oche.de/~akupries/soft/trf/>, # has several utilities for transforming data, # including message digests such as CRC and MD5. package require Trf binary scan [crc {an apple a day}] H* checksum set checksum # => 325295 # slowcat - emulate a s l o w line printer # usage: slowcat [-DELAY] [files ...] proc slowcat {args} { set delay 1 if {[llength $args]} { if {[regexp {^-([.\d]+)} [lindex $args 0] match delay]} { set args [lrange $args 1 end] } } fconfigure stdout -buffering no if {[llength $args]} { foreach {arg} $args { set f [open $arg] lappend channels $f } } else { set channels stdin } foreach {chan} $channels { while {[gets $chan line] > -1} { foreach {ch} [split $line {}] { puts -nonewline $ch after [expr {int(5 * $delay)}] } puts {} } } } |
proc reverse {args} { set res [list] if {[llength $args] == 1} { set args [lindex $args 0] } foreach elem $args { set res [linsert $res 0 $elem] } return $res } # reverse characters join [reverse [split $string {}]] {} # reverse words join [reverse [split $string]] # reverse quoted words join [reverse [split {Yoda said, "can you see this?"}]] # => this?" see you "can said, Yoda set word reviver set is_palindrome [string equal $word [join [reverse [split $word]]]] # => 1 |
package require textutil namespace import ::textutil::tabify::* tabify "... zzz xxx" # => ... zzz xxx untabify "...\tzzz\txxx" # => ... zzz xxx tabify2 "... zzz xxx" # => ... zzz xxx untabify2 "...\tzzz\txxx" # => ... zzz xxx |
set debt 100 subst "You owe $debt to me." # => You owe 100 to me. set debt 100 proc writeIt {string} { uplevel subst [list $string] } # braces prevent immediate substitution writeIt {You owe $debt to me.} # => You owe 100 to me. foreach {rows cols} {24 80} break set text {I am $rows high and $cols long} subst $text # => I am 24 high and 80 long set string "I am 17 years old" regsub -all {(\d+)} $string {[expr {\1*2}]} string subst $string # => I am 34 years old # expand variables in $text, but put an error message in # if the variable isn't defined. proc expandOrError-1 {@text} { upvar ${@text} text while {[regexp {\$(\w+)} $text match var]} { if {[uplevel info exists $var]} { regsub \\$match $text [uplevel set $var] text } else { regsub \\$match $text "\[NO VARIABLE: $var\]" text } } set text } # Tcl allows commands to embedded in text data as well as variables. # If the string is taken from user input, this may be a security # hazard. The solution is to let a "safe interpreter" (which has a # reduced set of commands by default) interpret the text data. In # case the interpreted script text contains illegal commands the # interpreter raises an error. proc safeExpand-1 {string} { set si [interp create -safe] set res [uplevel $si eval [list subst [list $string]]] interp delete $si set res } safeExpand-1 {[exec rm foo.bar]} # => invalid command name "exec" # It is also possible to further reduce the command set of an # interpreter, or to add new commands, or to change the meaning # of commands (i.e. exec would perform *some* system commands # but not all, etc). # But I digress... |
set little "bo peep" set big [string toupper $little] # => BO PEEP set big "BO PEEP" set little [string tolower $big] # => bo peep set little "bo peep" set title [string totitle $little] # => Bo peep set little "bo peep" set big [string toupper $little 0] # => Bo peep set big "BO PEEP" set little [string tolower $big 0] # => bO PEEP # convert case within a string set name {kirk} set string "Ship's Captain: [string totitle $name]." # => Ship's Captain: Kirk. # capitalize each word's first character, downcase the rest set text "thIS is a loNG liNE" set pos 0 while {[regexp -indices -start $pos {(\w+)} $text where]} { foreach {first last} $where break set text [string totitle $text $first $last] set pos $last incr pos } puts $text # => This Is A Long Line # capitalize each word's first character, downcase the rest # (another solution) foreach word "thIS is a loNG liNE" { lappend words [string totitle $word] } puts $words # => This Is A Long Line # case insensitive string comparison string equal -nocase foo Foo # => 1 # randcap: filter to randomly capitalize 20% of the letters set text { 001:001 In the beginning God created the heaven and the earth. 001:002 And the earth was without form, and void; and darkness was upon the face of the deep. And the spirit of God moved upon the face of the waters. 001:003 And God said, let there be light: and there was light. } set pos 0 while {[regexp -indices -start $pos {(\w)} $text where]} { foreach {first last} $where break if {rand()<=0.2} { set text [string toupper $text $first] } else { set text [string tolower $text $first] } set pos $last incr pos } puts $text # => # => 001:001 iN The begInNing god crEaTed tHe HeAven And thE earTh. # => # => 001:002 and tHe earth was wiThout form, aNd void; and darknESs Was # => upOn tHe faCe OF the deep. and the sPirIt Of goD moved upOn # => the fACE oF the wATers. # => # => 001:003 AnD goD said, lEt there be light: aND there wAs LighT. # => |
# Interpolating functions and expressions within strings set var1 Tool proc func {s} {string totitle $s} set var2 Language set answer "$var1 [func command] $var2" # => Tool Command Language set n 5 set phrase "I have [expr {$n + 1}] guanacos." # => I have 6 guanacos. set rec foo:bar:baz interp alias {} some_cmd {} join some_cmd "What you want is [llength [split $rec :]] items" # => What you want is 3 items set text { To: $naughty From: Your Bank Cc: [getManagerList $naughty] Date: [clock format [clock seconds]] (today) Dear $naughty, Today, you bounced check number [expr {500 + int(rand()*100)}] to us. Your account is now closed. Sincerely, the management } if {![sendMail $text $target]} { error "Couldn't send mail" } |
# all in one regsub -line -all {^\s+} { your text goes here } {} var format %s \n$var # => # => your text # => goes here # or with two steps set var { your text goes here } regsub -line -all {^\s+} $var {} var format %s \n$var # => # => your text # => goes here # one more time regsub -line -all {^\s+} { The five varieties of camelids are the familiar camel, his friends the llama and the alpaca, and the rather less well-known guanaco and vicuña. } {} definition # => 6 proc fix {string} { regsub -line -all {^\s+} $string {} string return $string } fix { My stuff goes here } # => My stuff goes here # the end-of-string right brace doesn't have to be flush left regsub -line -all {^\s+} { ...we will have peace, when you and all your works have perished--and the works of your dark master to whom you would deliver us. You are a liar, Saruman, and a corrupter of men's hearts. --Theoden in /usr/src/perl/taint.c } {} quote ;# <-- looki looki # move attribution to line of its own regsub {\s+--} $quote \n-- quote format %s \n$quote # => # => ...we will have peace, when you and all your works have # => perished--and the works of your dark master to whom you would # => deliver us. You are a liar, Saruman, and a corrupter of men's # => hearts. # => --Theoden in /usr/src/perl/taint.c proc rememberTheMain {} { dequote { @@@ int @@@ runops() { @@@ SAVEI32(runlevel); @@@ runlevel++; @@@ while ( op = (*op->op_ppaddr)() ) ; @@@ TAINT_NOT; @@@ return 0; @@@ } } # add more code here if you want } proc roadGoesEverOn {} { dequote { Now far ahead the Road has gone, And I must follow, if I can, Pursuing it with eager feet, Until it joins some larger way Where many paths and errands meet. And whither then? I cannot say. --Bilbo in /usr/src/perl/pp_ctl.c } } proc quotemeta {string} { regsub -all {(\W)} $string {\\\1} string return $string } proc dequote {text} { if {[regexp -line {^\s*(?:([^\w\s]+)(\s*).*\n)(?:\s*\1\2?.*\n)+(?:\s*)$} $text m 1 2]} { set white $2 set leader [quotemeta $1] } else { regexp -line {^\n?(\s+)} $text m white set leader {} } regsub -line -all ^\\s*?$leader\(?:$white\) $text {} text return [string trimright $text]\n } |
# The tcllib 1.0 textutil module can adjust text # to a specified line length, justify left, right, # and plain, and fill lines to the right margin. # However, it can't add indentation. # A naive implementation of wrap. Arguments: # text the text # col the line length (default 72) # lead first line indentation (def empty string) # follow indentation for following lines (def empty string) proc wrap {text {col 72} {lead {}} {follow {}}} { set newtext {} set text $lead[string trimleft $text] set upto $col while {![string is wordchar [string index $text $upto]]} {incr upto -1} set upto [string wordstart $text $upto] if {$upto == 0} { set upto [string wordend $text $upto] } else { incr upto -1 } append newtext [string range $text 0 $upto]\n set text [string replace $text 0 $upto] while {[string length $text]} { set text $follow[string trimleft $text] if {[string length $text] > $col} { set upto $col while {![string is wordchar [string index $text $upto]]} {incr upto -1} set upto [string wordstart $text $upto] if {$upto == 0} { set upto [string wordend $text $upto] } else { incr upto -1 } append newtext [string range $text 0 $upto]\n set text [string replace $text 0 $upto] } else { append newtext $text set text {} } } return $newtext } set input {"Folding and splicing is the work of an editor," "not a mere collection of silicon" "and" "mobile electrons!"} append res \n [string repeat 0123456789 2] \n append res [wrap [join $input] 20 { } { }] \n # => # => 01234567890123456789 # => Folding and # => splicing is the # => work of an # => editor, not a # => mere collection # => of silicon and # => mobile electrons! |
# backslash regsub -all (\[$charlist]) $var {\\\1} var # double regsub -all (\[$charlist]) $var {\1\1} var set string {Mom said, "Don't do that."} regsub -all {(['"])} $string {\\\1} string puts $string # => Mom said, \"Don\'t do that.\" set string {Mom said, "Don't do that."} regsub -all {(['"])} $string {\1\1} string puts $string # => Mom said, ""Don''t do that."" set string {Mom said, "Don't do that."} regsub -all {([^A-Z])} $string {\\\1} string ;# or: ([^[:upper:]]) puts $string # => M\o\m\ \s\a\i\d\,\ \"D\o\n\'\t\ \d\o\ \t\h\a\t\.\" regsub -all {([^[:alnum:]])} "is a test!" {\\\1} string puts "this $string" # => this is\ a\ test\! |
string trim "\n\t Tcl \t\n" # => Tcl set string { foo bar baz } set res [list] foreach {s} [split $string \n] { lappend res [string trim $s] } string trim [join $res] # => foo bar baz # The [gets] (get string) command always strips off the EOL # sequence, be it CR, LF, or CRLF (configurable for the stream). # Anyway, if you have a string that *might* have one or more # \n characters at the end, and in case it does, you want to # remove them: string trimright "foo bar\n\n" \n # => foo bar |
# csv is a part of the standard ActiveTcl distribution package require csv set line {XYZZY,,"O'Reilly, Inc","Wall, Larry","a ""glug"" bit",5,"Error, Core Dumped"} set fields [::csv::split $line] set res {} for {set i 0} {$i < [llength $fields]} {incr i} { append res \n "$i : [lindex $fields $i]" } puts $res # => # => 0 : XYZZY # => 1 : # => 2 : O'Reilly, Inc # => 3 : Wall, Larry # => 4 : a "glug" bit # => 5 : 5 # => 6 : Error, Core Dumped |
# fixstyle - switch one set of strings to another set # usage: <scriptname> [-v] [files ...] array set ::data { analysed analyzed built-in builtin chastized chastised commandline command-line de-allocate deallocate dropin drop-in hardcode hard-code meta-data metadata multicharacter multi-character multiway multi-way non-empty nonempty non-profit nonprofit non-trappable nontrappable pre-define predefine preextend pre-extend re-compiling recompiling reenter re-enter turnkey turn-key } set testtext { Yesterday we analysed the efficiency of the built-in thingummies and were considerably chastized by the results. It seems that commandline invocation forced the whatchamacallit to de-allocate dropin maguffins. First, we tested instead to hardcode meta-data -- especially when in multicharacter and multiway format (obviously only for non-empty data sets). However, that turned out to be a non-profit improvement. Dr Egnarts then demonstrated using non-trappable signals in pre-define mode to preextend save rates. When re-compiling we saw the application reenter acceptable ratings on turnkey operations. } # verbose or non-verbose? if {[llength $argv] && [string equal [lindex $argv 0] -v]} { set ::verbose yes set argv [lrange $argv 1 end] } else { set ::verbose no } # prepare text to be read set text {} if {[string match *test [info script]]} { # if we're running a test: set text $testtext } else { # Try to assemble text from input. Do we have arguments? if {[info exists argv]} { # Yes; try to open each and read contents: foreach {fn} [lrange $argv 0 end] { if {![catch {open $fn} chan]} { append text [read $chan] close $chan } } } if {![string length $text]} { # we still don't have any text; try standard input # (inform user first) if {[tell stdin] == -1} { puts stderr "[info script]: Reading from stdin" } set text [read stdin] } } proc fixstyle {text} { global data verbose set newtext [list] foreach w $text { if {[catch {set word $data($w)}]} { set word $w } else { if {$verbose} { puts stderr "$w => $word" } } lappend newtext $word } return $newtext } fixstyle $text # => Yesterday we analyzed the efficiency of the builtin thingummies and were considerably chastised by the results. It seems that command-line invocation forced the whatchamacallit to deallocate drop-in maguffins. First, we tested instead to hard-code metadata -- especially when in multi-character and multi-way format (obviously only for nonempty data sets). However, that turned out to be a nonprofit improvement. Dr Egnarts then demonstrated using nontrappable signals in predefine mode to pre-extend save rates. When recompiling we saw the application re-enter acceptable ratings on turn-key operations. |