Created
May 7, 2010 14:47
-
-
Save et/393502 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# -*- tcl -*- | |
# @@PLEAC@@_NAME | |
# @@SKIP@@ Tcl | |
# @@PLEAC@@_WEB | |
# @@SKIP@@ http://tcl.tk/ | |
# @@PLEAC@@_INTRO | |
# @@SKIP@@ Version: Tcl 8.4 | |
# @@PLEAC@@_APPENDIX | |
# @@SKIP@@ General-purpose, custom functions that might be used in several sections, appear here | |
# coroutines. | |
# recursion messes with the uplevel stuff. so using this imperative | |
# version instead. | |
# what we have here is an eqivalent of ruby's str.gsub! &block mechanism, | |
# where each matched string is passed into the block and the results are | |
# used for substitution. | |
proc gregsub {re txt block} { | |
set res {} | |
while 1 { | |
#fetch the regexp first | |
set part [lindex [regexp -inline $re $txt] 1] | |
if {![string length $part]} { | |
append res $txt | |
break | |
} | |
#now substitute with original | |
set lst [split [regsub -- $re $txt "\0"] "\0"] | |
append res [lindex $lst 0] [apply $block $part] | |
set txt [lindex $lst 1] | |
} | |
return $res | |
} | |
proc regrange {p1 sep p2 data block} { | |
set on 0 | |
set delay 0 | |
if {![string compare $sep "..."]} { | |
set delay 1 | |
} | |
if ![llength $p1] { ;# {} for start from begining. | |
set on 1 | |
set p1 {$-^} ;# never match any thing more. | |
} | |
foreach line $data { | |
switch -exact -- $sep { | |
{..} { | |
if {[regexp -- $p1 $line]} {set on 1} elseif {[regexp -- $p2 $line]} {set delay 1} | |
if {$on} { | |
#do thingies. | |
apply $block $line | |
} | |
if {$delay} { | |
set on 0 | |
set delay 0 | |
} | |
} | |
{...} { | |
if {[regexp -- $p1 $line]} {set delay 0} elseif {[regexp -- $p2 $line]} {set on 0} | |
if {$on} { | |
#do thingies. | |
apply $block $line | |
} | |
if {!$delay} { | |
set on 1 | |
set delay 1 | |
} | |
} | |
default { | |
error "wrong range operator $sep" | |
} | |
} | |
} | |
} | |
proc with-file {file block} { | |
set fd [open $file] | |
uplevel 1 [list apply $block $fd] | |
close $fd | |
} | |
proc read-lines {fd block} { | |
while {[gets $fd line] >= 0} { | |
uplevel 1 [list apply $block $line] | |
} | |
} | |
proc readlines {fd block} { | |
set data [read -nonewline $fd] | |
set variable options | |
set cr "\n" | |
if [info exist options(CR)] { | |
set cr $options(CR) | |
} | |
foreach line [split [regsub -all -- $cr $data "\0" ] "\0" ] { | |
uplevel 1 [list apply $block $line] | |
puts -nonewline $cr | |
} | |
} | |
proc argf-iter {block} { | |
variable options | |
foreach file $::argv { | |
with-file $file [list fd "return \[readlines \$fd {$block}\]"] | |
} | |
} | |
# @@PLEAC@@_1.0 | |
# 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 (\}) | |
} | |
# @@PLEAC@@_1.1 | |
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* | |
# @@PLEAC@@_1.2 | |
# 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 | |
} | |
# @@PLEAC@@_1.3 | |
# 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 | |
# @@PLEAC@@_1.4 | |
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 | |
# @@PLEAC@@_1.5 | |
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 {} | |
} | |
} | |
} | |
# @@PLEAC@@_1.6 | |
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 | |
# @@PLEAC@@_1.7 | |
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 | |
# @@PLEAC@@_1.8 | |
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... | |
# @@PLEAC@@_1.9 | |
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. | |
# => | |
# @@PLEAC@@_1.10 | |
# 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" | |
} | |
# @@PLEAC@@_1.11 | |
# 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 | |
} | |
# @@PLEAC@@_1.12 | |
# 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! | |
# @@PLEAC@@_1.13 | |
# 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\! | |
# @@PLEAC@@_1.14 | |
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 | |
# @@PLEAC@@_1.15 | |
# 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 | |
# @@PLEAC@@_1.17 | |
# 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. | |
# @@PLEAC@@_2.0 | |
# @@INCOMPLETE@@ | |
# @@INCOMPLETE@@ | |
# @@PLEAC@@_2.1 | |
# The "backwards conditional" Perl form is useful here | |
# to demonstrate the various regexps. Tcl doesn't have | |
# this syntax, but it can be fudged very easily: | |
proc warn {msg cond pattern {string 0}} { | |
if {[string equal if $cond]} { | |
if {[regexp $pattern $string]} { | |
return [format "%s: %s" $string $msg] | |
} | |
} elseif {[string equal unless $cond]} { | |
if {![regexp $pattern $string]} { | |
return [format "%s: %s" $string $msg] | |
} | |
} | |
return | |
} | |
warn "has nondigits" if {\D} | |
warn "not a natural number" unless {^\d+$} ;# rejects -3 | |
warn "not an integer" unless {^[+-]?\d+$} ;# rejects +3 | |
warn "not a real number" unless {^-?\d+\.?\d*$} ;# rejects .2 | |
warn "not a C float" unless {^([+-]?)(?=\d|\.\d)\d*(\.\d*)?([Ee]([+-]?\d+))?$} | |
# Regexps like the above are sometimes necessary for making fine | |
# distinctions among string representations of numbers. | |
# If the only thing in questions is whether $x is a number | |
# or not, or whether it is an integer or a real number, Tcl | |
# can help: | |
if {[string is integer $x]} { | |
set res "$x is an integer" | |
} elseif {[string is double $x]} { | |
set res "$x is a real number" | |
} else { | |
set res "$x is not a number" | |
} | |
set res | |
# @@PLEAC@@_2.2 | |
# limit number of decimals when determining equality of | |
# floating point values to avoid rounding errors. | |
proc floatEqual-1 {num1 num2 accuracy} { | |
expr {[format %.${accuracy}f $num1] == [format %.${accuracy}f $num2]} | |
} | |
set wage 536 ;# $5.36/hour | |
set week [expr {40 * $wage}] ;# $214.40 | |
format "One week's wage is: \$%.2f" [expr {$week/100.0}] | |
# => One week's wage is: $214.40 | |
# @@PLEAC@@_2.3 | |
set a 0.255 | |
set b [format %.2f $a] | |
puts "Unrounded: $a" | |
puts "Rounded: $b" | |
# => Unrounded: 0.255 | |
# => Rounded: 0.26 | |
set res \nnumber\tint\tfloor\tceil\n | |
set a [list 3.3 3.5 3.7 -3.3] | |
foreach n $a { | |
append res [format %.1f\t $n] | |
append res [format %.1f\t [expr {int($n)}]] | |
append res [format %.1f\t [expr {floor($n)}]] | |
append res [format %.1f\n [expr {ceil($n)}]] | |
} | |
puts $res | |
# => | |
# => number int floor ceil | |
# => 3.3 3.0 3.0 4.0 | |
# => 3.5 3.0 3.0 4.0 | |
# => 3.7 3.0 3.0 4.0 | |
# => -3.3 -3.0 -4.0 -3.0 | |
# @@PLEAC@@_2.4 | |
proc dec2bin {string} { | |
binary scan [binary format I $string] B32 str | |
return [string trimleft $str 0] | |
} | |
dec2bin 54 | |
# => 110110 | |
proc bin2dec {string} { | |
set string [format %032s $string] | |
binary scan [binary format B32 $string] I str | |
return $str | |
} | |
bin2dec 110110 | |
# => 54 | |
# @@PLEAC@@_2.5 | |
for {set i $X} {$i <= $Y} {incr i} { | |
# $i is set to every integer from X to Y, inclusive | |
} | |
for {set i $X} {$i <= $Y} {incr i 7} { | |
# $i is set to every integer from X to Y, stepsize = 7 | |
} | |
set res {} | |
append res "Infancy is: " | |
foreach i [list 0 1 2] { | |
append res "$i " | |
} | |
proc .. {low high} { | |
for {set i $low} {$i <= $high} {incr i} { | |
lappend res $i | |
} | |
set res | |
} | |
append res \n | |
append res "Toddling is: " | |
foreach i [.. 3 4] { | |
append res "$i " | |
} | |
append res \n | |
append res "Childhood is: " | |
for {set i 5} {$i <= 12} {incr i} { | |
append res "$i " | |
} | |
puts $res | |
# => Infancy is: 0 1 2 | |
# => Toddling is: 3 4 | |
# => Childhood is: 5 6 7 8 9 10 11 12 | |
# @@PLEAC@@_2.6 | |
# These procedures were written by Richard Suchenwirth. | |
# See <URL: http://mini.net/tcl/1749.html> | |
roman:number 15 | |
# => XV | |
roman:get XV | |
# => 15 | |
# @@PLEAC@@_2.7 | |
# The rand function returns a floating point number from zero to | |
# just less than one or, in mathematical notation, the range [0,1). | |
# The seed comes from the internal clock of the machine or may be | |
# set manually with the srand function. | |
# The math module of the standard distribution has a wrapper for | |
# rand called random; it supports generation of pseudo-random | |
# numbers in the [0,n) and [n,m) ranges. | |
puts [expr {int(rand()*51)+25}] | |
# => 32 | |
package require math | |
puts [::math::random 25 76] | |
# => 32 | |
set list [split {Demonstrate selecting a random element from a list.}] | |
package require math | |
puts [lindex $list [::math::random [llength $list]]] | |
# => selecting | |
package require math | |
set password {} | |
for {set i 0} {$i < 8} {incr i} { | |
append password [lindex $chars [::math::random [llength $chars]]] | |
} | |
puts $password | |
# => JhzQ!p!$ | |
# @@PLEAC@@_2.8 | |
set value 1138 | |
expr {srand($value)} | |
# => 0.00890640821723 | |
# @@PLEAC@@_2.9 | |
# There is no standard module known to me that implements better | |
# random number generators than the one in the C library, but at | |
# <URL: http://www.elf.org/etc/randomnumbers.html> there is Tcl | |
# and C source for a ``very long period random number generator''. | |
# Also see <URL: http://mini.net/cgi-bin/wikit/1551.html> for a | |
# `post-processor' that improves the randomness of the output of | |
# rand(). | |
# @@INCOMPLETE@@ | |
# @@PLEAC@@_2.10 | |
# @@INCOMPLETE@@ | |
# @@INCOMPLETE@@ | |
# @@PLEAC@@_2.11 | |
# You'd typically want a variable like PI to be | |
# contained within a namespace and not automatically | |
# set in the global namespace. [variable] creates | |
# a variable in the current namespace, and [namespace | |
# current] returns the qualified name of the current | |
# namespace, or :: for the global namespace. | |
variable PI [expr {acos(-1)}] | |
puts [set [namespace current]::PI] | |
# => 3.14159265359 | |
proc deg2rad {degrees} { | |
variable PI | |
return [expr {$degrees / 180.0 * $PI}] | |
} | |
proc rad2deg {radians} { | |
variable PI | |
return [expr {$radians / $PI * 180}] | |
} | |
# The core Tcl command [expr] has most of the commonly | |
# used trigonometric functions defined, so there is | |
# less need for a Trig module. | |
proc degreeSine {degrees} { | |
set radians [deg2rad $degrees] | |
return [expr {sin($radians)}] | |
} | |
# @@PLEAC@@_2.12 | |
# The tangent function is already available in the [expr] | |
# command, as is the arcus cosine and many more. | |
# In some cases, the [expr] functions raise an error because | |
# of overflow or division by zero. To trap such errors, wrap | |
# in [catch]: | |
list [catch {expr {1/0}} msg] $msg | |
# => 1 {divide by zero} | |
# @@PLEAC@@_2.13 | |
set value 1138 | |
puts [expr {log($value)}] | |
# => 7.03702761469 | |
set value 1138 | |
puts [expr {log10($value)}] | |
# => 3.05614226206 | |
proc logN {base value} { | |
return [expr {log($value) / log($base)}] | |
} | |
# @@PLEAC@@_2.14 | |
# There are a few non-standard matrix modules available for Tcl, e.g. | |
# * TiM: <URL: http://www-obs.univ-lyon1.fr/~thiebaut/TiM/TiM.html>. | |
# In TiM, matrix multiplication seems to be an "A * B" matter. | |
# * La (The Hume Linear Algebra Tcl Package): | |
# <URL: http://www.hume.com/la/index.html>. Matrix multiplication | |
# in La looks like this: mmult A B. | |
# There is also a matrix module in the standard distribution library, | |
# but it does not contain arithmetic. I have used it anyway, with | |
# an adaptation of the mmult subroutine in the Perl Cookbook. | |
package require struct 1.1.1 | |
proc mmult {m1 m2} { | |
set m1rows [$m1 rows] | |
set m1cols [$m1 columns] | |
set m2rows [$m2 rows] | |
set m2cols [$m2 columns] | |
if {$m1cols != $m2rows} { | |
error "IndexError: matrices don't match: $m1cols != $m2rows" | |
} | |
::struct::matrix result | |
result add rows $m1rows | |
result add columns $m2cols | |
for {set i 0} {$i < $m1rows} {incr i} { | |
for {set j 0} {$j < $m2cols} {incr j} { | |
set v 0 | |
for {set k 0} {$k < $m1cols} {incr k} { | |
incr v [expr {[$m1 get cell $k $i] * [$m2 get cell $j $k]}] | |
} | |
result set cell $j $i $v | |
} | |
} | |
return result | |
} | |
::struct::matrix x | |
x add columns 3 | |
x add row [list 3 2 3] | |
x add row [list 5 9 8] | |
::struct::matrix y | |
y add rows 3 | |
y add column [list 4 9 3] | |
y add column [list 7 3 1] | |
set res [mmult x y] | |
$res get rect 0 0 end end | |
# => {39 30} {125 70} | |
# @@PLEAC@@_2.15 | |
# See <URL: http://www.mini.net/tcl/Complex> for complex | |
# arithmetic routines by Richard Suchenwirth. | |
complex::* 3+5i 2-2i | |
# => 16+4i | |
# @@PLEAC@@_2.16 | |
# Tcl does not have hex/oct functions, but | |
# they are easy to implement. If [expr] | |
# gets handed an invalid octal/hex number, | |
# it raises an error instead of returning | |
# 0 as the Perl functions do. | |
proc hex {string} { | |
if {[regexp -nocase {^0x} $string]} { | |
return [expr $string] | |
} else { | |
return [expr 0x$string] | |
} | |
} | |
# This simpler version does not raise errors for invalid input: | |
# proc hex {string} { | |
# scan $string %x | |
# } | |
proc oct {string} { | |
if {[regexp -nocase {^0x} $string]} { | |
return [hex $string] | |
} else { | |
return [expr 0$string] | |
} | |
} | |
# This simpler version does not raise errors for invalid input: | |
# proc oct {string} { | |
# scan $string %o | |
# } | |
if {[string match *.test [info script]]} { | |
# we are testing, supply known value | |
set num 0x39a | |
} else { | |
puts "Gimme a number in decimal, octal, or hex: " | |
set num [gets stdin] | |
} | |
if {[string length $num]} { | |
if {[regexp ^0 $num]} { | |
set num [oct $num] | |
} | |
format "%d %x %o" $num $num $num | |
} | |
# => 922 39a 1632 | |
# @@PLEAC@@_2.17 | |
# This procedure is written by Keith Vetter and is part of the Tcl | |
# Cookbook (<URL: #http://aspn.activestate.com/ASPN/Cookbook/Tcl/Recipe/68381>) | |
proc comma {num {sep ,}} { | |
while {[regsub {^([-+]?\d+)(\d\d\d)} $num "\\1$sep\\2" num]} {} | |
return $num | |
} | |
# @@PLEAC@@_2.18 | |
# See <URL: http://mini.net/tcl/EnglishPlurals> for an | |
# English pluralization function by Richard Suchenwirth. | |
set data [join {fish fly ox | |
species genus phylum | |
cherub radius jockey | |
index matrix mythos | |
phenomenon formula}] | |
set res {} | |
foreach word $data { | |
append res "One $word, two [en:pl $word]\n" | |
} | |
puts $res | |
# => One fish, two fish | |
# => One fly, two flies | |
# => One ox, two oxen | |
# => One species, two species | |
# => One genus, two genera | |
# => One phylum, two phyla | |
# => One cherub, two cherubim | |
# => One radius, two radii | |
# => One jockey, two jockeys | |
# => One index, two indices | |
# => One matrix, two matrices | |
# => One mythos, two mythoi | |
# => One phenomenon, two phenomena | |
# => One formula, two formulae | |
# @@PLEAC@@_2.19 | |
# See <URL: http://www.mini.net/tcl/AdditionalMath>: | |
# the primefactors function by Richard Suchenwirth. | |
primefactors 2178 | |
# => 2 3 3 11 11 | |
primefactors 2099999990 | |
# => 2 5 11 19090909 | |
# @@PLEAC@@_3.0 | |
# A single command, [clock], is used for a wide range | |
# of date/time-related tasks. Subcommands include | |
# seconds, which returns a seconds-since-epoch value, | |
# and format, which formats a date/time-string like | |
# the result of POSIX strftime. | |
# get current time in epoch seconds | |
set now [clock seconds] | |
# print default-formatted time | |
puts [clock format $now] | |
# print custom formatted time | |
set fmt "Today is day %j of the current year." | |
puts [clock format $now -format $fmt] | |
# @@PLEAC@@_3.1 | |
set now [clock seconds] | |
foreach {day month year} [clock format $now -format "%d %m %Y"] break | |
set now [clock seconds] | |
set fmt "%Y-%m-%d" | |
puts "The current date is [clock format $now -format $fmt]." | |
# @@PLEAC@@_3.2 | |
# this is one of several possible variants of scannable | |
# date/time strings; clock scan is considerably more | |
# versatile than the Perl functions in this recipe. | |
set time [clock scan "$hours:$min:$sec $year-$mon-$mday"] | |
# => 999955820 | |
set time [clock scan "$hours:$min:$sec $year-$mon-$mday" -gmt yes] | |
# => 999963020 | |
# @@PLEAC@@_3.3 | |
if {[string match *.test [info script]]} { | |
# we are testing, supply a known value | |
set now 1000000000 | |
} else { | |
set now [clock seconds] | |
} | |
set vars [list seconds minutes hours dayOfMonth month year wday yday] | |
set desc [list S M H d m Y w j] | |
foreach v $vars d $desc { | |
set $v [clock format $now -format %$d] | |
} | |
format %s-%s-%sT%s:%s:%s $year $month $dayOfMonth $hours $minutes $seconds | |
# => 2001-09-09T03:46:40 | |
if {[string match *.test [info script]]} { | |
# we are testing, supply a known value | |
set now 1000000000 | |
} else { | |
set now [clock seconds] | |
} | |
set vars [list seconds minutes hours dayOfMonth month year wday yday] | |
set desc [list S M H d m Y w j] | |
foreach v $vars d $desc { | |
set $v [clock format $now -format %$d -gmt yes] | |
} | |
format %s-%s-%sT%s:%s:%s $year $month $dayOfMonth $hours $minutes $seconds | |
# => 2001-09-09T01:46:40 | |
# @@PLEAC@@_3.4 | |
# set when [expr {$now + $difference}] | |
# set when [expr {$now - $difference}] | |
# The following is slightly more idiomatic: | |
# set when [clock scan "$difference seconds"] | |
# set when [clock scan "$difference seconds ago"] | |
# set when [clock scan "-$difference seconds"] ;# same as previous | |
set newTime [clock scan "$y-$m-$d $offset days"] | |
foreach {y2 m2 d2} [clock format $newTime -format "%Y %m %d"] break | |
return [list $y2 $m2 $d2] | |
set oldTime [clock scan $time] | |
set newTime [clock scan " | |
$daysOffset days | |
$hourOffset hours | |
$minuteOffset minutes | |
$secondOffset seconds | |
" -base $oldTime] | |
# @@PLEAC@@_3.5 | |
set bree [clock scan "16 Jun 1981 4:35:25"] | |
set nat [clock scan "18 Jan 1973 3:45:50"] | |
set difference [expr {$bree - $nat}] | |
format "There were $difference seconds between Nat and Bree" | |
# => There were 265333775 seconds between Nat and Bree | |
set bree [clock scan "16 Jun 1981 4:35:25"] | |
set nat [clock scan "18 Jan 1973 3:45:50"] | |
set difference [expr {$bree - $nat}] | |
set vars {seconds minutes hours days} | |
set factors {60 60 24 7} | |
foreach v $vars f $factors { | |
set $v [expr {$difference % $f}] | |
set difference [expr {($difference-[set $v]) / $f}] | |
} | |
set weeks $difference | |
format "($weeks weeks, $days days, $hours:$minutes:$seconds)" | |
# => (438 weeks, 4 days, 23:49:35) | |
# @@PLEAC@@_3.6 | |
set then [clock scan 6/16/1981] | |
set format { | |
%Y-%m-%d was a %A | |
in week number %W, | |
and day %j of the year. | |
} | |
clock format $then -format $format | |
# => | |
# => 1981-06-16 was a Tuesday | |
# => in week number 24, | |
# => and day 167 of the year. | |
# => | |
# @@PLEAC@@_3.7 | |
# The [clock scan] command parses a wide variety of date/time | |
# strings, converting them to epoch seconds. | |
# Examples: | |
# set t [clock scan "1998-06-03"] | |
# set t [clock scan "2 weeks ago Friday"] | |
# set t [clock scan "today"] | |
# # second Sunday of 1996: | |
# set t [clock scan "Sunday" -base [clock scan "1996-01-01 1 week"]] | |
# The result can be converted to lists of year, month, etc | |
# values or to other date/time strings by the [clock format] | |
# command. | |
# @@PLEAC@@_3.8 | |
puts [clock format [clock scan 01/18/73] -gmt yes] | |
# => Wed Jan 17 23:00:00 GMT 1973 | |
puts [clock format [clock scan 01/18/73] -format "%A %D"] | |
# => Thursday 01/18/73 | |
set format "%a %b %e %H:%M:%S %Z %Y" | |
puts [clock format [clock scan "18 Jan 1973 3:45:50 GMT"] -format $format -gmt yes] | |
# => Thu Jan 18 03:45:50 GMT 1973 | |
# @@PLEAC@@_3.9 | |
puts "Press return when ready" | |
set before [clock clicks -milliseconds] | |
gets stdin | |
set elapsed [expr {([clock clicks -milliseconds] - $before) / 1000.0}] | |
puts "You took $elapsed seconds" | |
set size 500 | |
set numberOfTimes 100 | |
set a [list] | |
for {set j 0} {$j < $size} {incr j} { | |
lappend a [expr {rand()}] | |
} | |
puts "Sorting $size random numbers:" | |
puts [time { | |
set a [lsort -real $a] | |
} $numberOfTimes] | |
# @@PLEAC@@_3.10 | |
# wait 25 milliseconds | |
after 25 | |
# @@PLEAC@@_3.11 | |
# @@INCOMPLETE@@ | |
# @@INCOMPLETE@@ | |
# @@PLEAC@@_4.0 | |
set presidents [list Reagan Bush Clinton] | |
# => Reagan Bush Clinton | |
set nested [list this that [list the other]] | |
llength $nested | |
# => 3 | |
set tune [list The Star-spangled Banner] | |
list #0 = [lindex $tune 0] #1 = [lindex $tune 1] | |
# => #0 = The #1 = Star-spangled | |
# @@PLEAC@@_4.1 | |
set a [list quick brown fox] | |
# => quick brown fox | |
set a "Why are you teasing me?" | |
# => Why are you teasing me? | |
set lines [list] | |
foreach {l} [split { | |
The boy stood on the burning deck, | |
It was as hot as glass. | |
} \n ] { | |
set line [string trimleft $l] | |
if {[string length $line]} { | |
lappend lines $line | |
} | |
} | |
puts $lines | |
# => {The boy stood on the burning deck,} {It was as hot as glass.} | |
set f [open $mydatafile] ;# Automatically raises error on failure | |
set biglist [split [read $f] \n] | |
lappend banner1 Costs only \$4.95 | |
set banner2 [list Costs only \$4.95] | |
set banner3 [split {Costs only $4.95}] | |
expr {"$banner1" == "$banner2" && "$banner2" == "$banner3"} | |
# => 1 | |
set ships [list Niña Pinta Santa María] ;# WRONG (4 ships) | |
llength $ships | |
# => 4 | |
set ships [list Niña Pinta {Santa María}] ;# right (3 ships) | |
llength $ships | |
# => 3 | |
# @@PLEAC@@_4.2 | |
set list [list red yellow green] | |
puts [list I have $list marbles.] | |
# => I have {red yellow green} marbles. | |
set list [list red yellow green] | |
puts "I have $list marbles." | |
# => I have red yellow green marbles. | |
set lists { | |
{{just one thing}} | |
{Mutt Jeff} | |
{Peter Paul Mary} | |
{{to our parents} {Mother Theresa} God} | |
{{pastrami} {ham and cheese} {peanut butter and jelly} {tuna}} | |
{{recycle tired, old phrases} {ponder big, happy thoughts}} | |
{{recycle tired, old phrases} {ponder big, happy thoughts} {sleep and dream peacefully} } | |
} | |
proc commifySeries {args} { | |
if {[regexp , $args]} { | |
set sepchar ";" | |
} else { | |
set sepchar , | |
} | |
# Tcl has a switch command, nyah nyah nyah | |
switch [llength $args] { | |
0 { return {} } | |
1 { eval return $args } | |
2 { return [join $args { and }] } | |
default { | |
set args [lreplace $args end end [concat and [lindex $args end]]] | |
return [join $args "$sepchar "] | |
} | |
} | |
} | |
# => just one thing | |
# => Mutt and Jeff | |
# => Peter, Paul, and Mary | |
# => to our parents, Mother Theresa, and God | |
# => pastrami, ham and cheese, peanut butter and jelly, and tuna | |
# => recycle tired, old phrases and ponder big, happy thoughts | |
# => recycle tired, old phrases; ponder big, happy thoughts; and sleep and dream peacefully | |
# @@PLEAC@@_4.3 | |
# There is no equivalent to $#ARRAY in Tcl. | |
proc whatAboutThatList args { | |
variable people | |
append res "The list now has [set len [llength $people]] elements.\n" | |
append res "The index of the last element is [incr len -1].\n" | |
append res "Element #3 is `[lindex $people 3]'." | |
} | |
set people [list Crosby Stills Nash Young] | |
whatAboutThatList | |
# => The list now has 4 elements. | |
# => The index of the last element is 3. | |
# => Element #3 is `Young'. | |
set people [lrange $people 0 end-1] | |
whatAboutThatList | |
# => The list now has 3 elements. | |
# => The index of the last element is 2. | |
# => Element #3 is `'. | |
# append 10001-(length of list) null elements to the list: | |
for {set i [llength $people]} {$i <= 10000} {incr i} { | |
lappend people {} | |
} | |
whatAboutThatList | |
# => The list now has 10001 elements. | |
# => The index of the last element is 10000. | |
# => Element #3 is `'. | |
# @@PLEAC@@_4.4 | |
foreach user $badUsers { | |
complain $user | |
} | |
foreach key [lsort [array names env]] { | |
puts $key=$env($key) | |
} | |
foreach {user} $allUsers { | |
set diskSpace [getUsage $user] | |
if {$diskSpace > $MAXQUOTA} { | |
complain $user | |
} | |
} | |
# Tcl has no implicit variables like Perl's $_. | |
foreach _ [exec who] { | |
if [regexp tchrist $_] { | |
puts $_ | |
} | |
} | |
# Tcl does not sneak in references unexpectedly. | |
# If you need to mutate a list, this is the preferred | |
# idiom: | |
# set mylist [mutate $mylist args] | |
# You *can* 'simulate' manipulation by reference by | |
# using call-by-name and connecting a local variable | |
# to a variable with that name in the outer scope: | |
proc timesSeven {listname} { | |
upvar $listname listref | |
for {set i 0} {$i < [llength $listref]} {incr i} { | |
set listref [lreplace $listref $i $i [expr {[lindex $listref $i] * 7}]] | |
} | |
} | |
# @@PLEAC@@_4.5 | |
variable res {} | |
set fruits [list Apple Blackberry] | |
set fruitRef fruits | |
# the variable fruitRef is now set to the name of the fruit list, | |
# which makes it a kind of reference variable | |
foreach fruit [set $fruitRef] { | |
append res "$fruit tastes good in a pie.\n" | |
} | |
puts $res | |
# => Apple tastes good in a pie. | |
# => Blackberry tastes good in a pie. | |
# @@PLEAC@@_4.6 | |
lsort -unique [list how much wood would a wood chuck chuck] | |
# => a chuck how much wood would | |
# This is an order of magnitude slower than the previous solution. | |
foreach e $list { | |
array set unique [list $e {}] | |
} | |
array names unique | |
# => a wood much chuck how would | |
# @@PLEAC@@_4.7 | |
# Use the TclX standard package (contained in | |
# the ActiveTcl distribution). | |
package require Tclx | |
set listA [list 1 1 2 2 3 3 3 4 5] | |
set listB [list 1 2 4] | |
set res [intersect3 $listA $listB] | |
# [intersect3] yields three result lists; | |
# we want the first one: | |
lindex $res 0 | |
# => 3 5 | |
# @@PLEAC@@_4.8 | |
# Use the TclX standard package (contained in | |
# the ActiveTcl distribution). | |
package require Tclx | |
set listA [list 1 1 2 2 3 3 3 4 5] | |
set listB [list 1 2 4 4 6 7] | |
foreach {difference intersection -} [intersect3 $listA $listB] break | |
set union [union $listA $listB] | |
list $difference $intersection $union | |
# => {3 5} {1 2 4} {1 2 3 4 5 6 7} | |
# @@PLEAC@@_4.9 | |
set members [list Time Flies] | |
lappend members An Arrow | |
# => Time Flies An Arrow | |
set members [list Time Flies] | |
set initiates [list An Arrow] | |
set members [concat $members $initiates] | |
# => Time Flies An Arrow | |
set members [list Time Flies An Arrow] | |
set members [linsert $members 2 Like] | |
# => Time Flies Like An Arrow | |
set members [list Time Flies Like An Arrow] | |
set members [lreplace $members 0 0 Fruit] | |
set members [lreplace $members end-1 end A Banana] | |
# => Fruit Flies Like A Banana | |
# @@PLEAC@@_4.10 | |
set list [list 0 1 2 3 4 5 6 7 8 9] | |
set rlist [list] | |
for {set i [expr {[llength $list]-1}]} {$i >= 0} {incr i -1} { | |
lappend rlist [lindex $list $i] | |
} | |
puts $rlist | |
# => 9 8 7 6 5 4 3 2 1 0 | |
set list [list 0 1 2 3 4 5 6 7 8 9] | |
lsort -decreasing $list | |
# => 9 8 7 6 5 4 3 2 1 0 | |
# @@PLEAC@@_4.11 | |
proc splice-ish {listname number} { | |
upvar $listname list | |
set length [llength $list] | |
if {$number < 0} { | |
set number [expr {abs($number)}] | |
set res [lrange $list end-[expr {$number-1}] end] | |
set list [lrange $list 0 end-$number] | |
} else { | |
set res [lrange $list 0 [expr {$number-1}]] | |
set list [lrange $list $number end] | |
} | |
return $res | |
} | |
proc shift2 {listname} { | |
upvar $listname list | |
return [splice-ish list 2] | |
} | |
set friends [list Peter Paul Mary Jim Tim] | |
foreach {this that} [shift2 friends] break | |
list $this $that $friends | |
# => Peter Paul {Mary Jim Tim} | |
proc pop2 {listname} { | |
upvar $listname list | |
return [splice-ish list -2] | |
} | |
set beverages [list Dew Jolt Cola Sprite Fresca] | |
set pair [pop2 beverages] | |
list $beverages $pair | |
# => {Dew Jolt Cola} {Sprite Fresca} | |
# @@PLEAC@@_4.12 | |
set matchIdx [lsearch $list $criterion] | |
if {$matchIdx >= 0} { | |
set match [lindex $list $matchIdx] | |
## do something with $match | |
} else { | |
## unfound | |
} | |
set matchIdx [lsearch $list $criterion] | |
if {$matchIdx >= 0} { | |
## found in [lindex $list $matchIdx] | |
} else { | |
## unfound | |
} | |
Employee is an [incr Tcl] class with the members category, | |
name, salary, ssn, and age. | |
lappend employees [Employee #auto {manager John 120000 {}}] | |
lappend employees [Employee #auto {engineer Susie 100000 {}}] | |
lappend employees [Employee #auto {programmer Harold 90000 {}}] | |
foreach employee $employees { | |
if {[$employee category] eq "engineer"} { | |
set highestEngineer $employee | |
break | |
} | |
} | |
$highestEngineer name | |
# => Susie | |
# @@PLEAC@@_4.13 | |
# If the test is matching an element's value against | |
# an exact string, a wildcard pattern, or a regular | |
# expression, use the standard package TclX (contained | |
# in the ActiveTcl distribution). | |
package require Tclx | |
set matching [lmatch [list ab ac bc dk ab] a*] | |
# => ab ac ab | |
# If another type of test is necessary, or TclX is | |
# unavailable, a foreach loop is useful: | |
# TEST could have been a regular proc, of course | |
interp alias {} TEST {} string match a* | |
set matching [list] | |
foreach e [list ab ac bc dk ab] { | |
if {[TEST $e]} { | |
lappend matching $e | |
} | |
} | |
set matching | |
# => ab ac ab | |
# @@PLEAC@@_4.14 | |
set numsorted [lsort -real [list 38 388.7 1.56 279 1e7]] | |
# => 1.56 38 279 388.7 1e7 | |
set descending [lsort -decreasing -real [list 38 388.7 1.56 279 1e7]] | |
# => 1e7 388.7 279 38 1.56 | |
# @@PLEAC@@_4.15 | |
# Generic code for using a custom comparison in a list sort: | |
# set ordered [lsort -command compare $unordered] | |
# Tcl doesn't have a standard map command as used by the following | |
# examples. | |
# Pool (<URL: http://www.purl.org/NET/akupries/soft/pool/index.htm>) | |
# includes a command, ::pool::list::apply, which is similar to Perl's | |
# map. | |
package require Pool_Base | |
namespace import ::pool::list::apply | |
set unordered [list 1+7 5-2 3+4] | |
proc compute e {list [expr $e] $e} | |
proc second args {lindex $args 1} | |
set precomputed [apply compute $unordered] | |
set orderedPrecomputed [lsort -integer -index 0 $precomputed] | |
set ordered [apply second $orderedPrecomputed] | |
# => 5-2 3+4 1+7 | |
Employee is an [incr Tcl] class with the members category, | |
name, salary, ssn, and age. | |
apply names $employees | |
# => Betsy Ewan Fran Andy Carl Diane | |
set ordered [lsort -command Employee::compare-name $employees] | |
apply names $ordered | |
# => Andy Betsy Carl Diane Ewan Fran | |
foreach employee [lsort -command Employee::compare-name $employees] { | |
puts "[$employee name] earns \$[$employee salary]" | |
} | |
# => Andy earns $110000 | |
# => Betsy earns $120000 | |
# => Carl earns $90000 | |
# => Diane earns $80000 | |
# => Ewan earns $115000 | |
# => Fran earns $110000 | |
set sortedEmployees [lsort -command Employee::compare-name $employees] | |
foreach employee $sortedEmployees { | |
puts "[$employee name] earns \$[$employee salary]" | |
} | |
# load bonus array | |
foreach employee $sortedEmployees { | |
if {[info exists bonus([$employee ssn])]} { | |
puts "[$employee name] got a bonus!" | |
} | |
} | |
# => Andy earns $110000 | |
# => Betsy earns $120000 | |
# => Carl earns $90000 | |
# => Diane earns $80000 | |
# => Ewan earns $115000 | |
# => Fran earns $110000 | |
# => Ewan got a bonus! | |
# => Fran got a bonus! | |
# The class procedure Employee::compare-name-or-age looks | |
# like this: | |
# proc compare-name-or-age {a b} { | |
# set cmp [string compare [[namespace parent]::$a name] [[namespace parent]::$b name]] | |
# if {$cmp != 0} { | |
# return $cmp | |
# } else { | |
# return [expr {[[namespace parent]::$a age]-[[namespace parent]::$b age]}] | |
# } | |
# } | |
lappend employees [Employee #auto {{} Andy 95000 28}] ;# add another Andy | |
set sorted [lsort -command Employee::compare-name-or-age $employees] | |
apply names-and-ages $sorted | |
# => {Andy 28} {Andy 30} {Betsy 43} {Carl 30} {Diane 27} {Ewan 37} {Fran 35} | |
# @@PLEAC@@_4.16 | |
set circular [concat [lrange $list 1 end] [lindex $list 0]] | |
set circular [concat [lindex $list end] [lrange $list 0 end-1]] | |
proc grabAndRotate {listname} { | |
upvar $listname list | |
set first [lindex $list 0] | |
set list [concat [lrange $list 1 end] $first] | |
return $first | |
} | |
while 1 { | |
set process [grabAndRotate processes] | |
puts "Handling process $process" | |
after 1000 | |
} | |
# @@PLEAC@@_4.17 | |
proc FisherYatesShuffle {listname} { | |
upvar $listname list | |
for {set i [expr {[llength $list]-1}]} {$i >= 0} {incr i -1} { | |
set j [expr {int(rand()*$i+1)}] | |
if {$i != $j} { | |
set temp [lindex $list $i] | |
set list [lreplace $list $i $i [lindex $list $j]] | |
set list [lreplace $list $j $j $temp] | |
} | |
} | |
} | |
# Several shuffle algorithms in Tcl are compared for performance | |
# here: <URL: http://mini.net/cgi-bin/nph-wikit/941.html>. | |
# This is a very efficient algorithm for small lists: | |
proc K {x y} {return $x} | |
proc shuffle5a { list } { | |
set n 1 | |
set slist {} | |
foreach item $list { | |
set index [expr {int(rand()*$n)}] | |
set slist [linsert [K $slist [set slist {}]] $index $item] | |
incr n | |
} | |
return $slist | |
} ;# Christoph Bauer | |
# @@PLEAC@@_4.18 | |
# @@INCOMPLETE@@ | |
# @@INCOMPLETE@@ | |
# @@PLEAC@@_4.19 | |
package require math 1.2 | |
# n2pat N len : produce the $N-th pattern of length $len | |
proc n2pat {N len} { | |
set i 1 | |
set pat [list] | |
while {$i <= $len + 1} { | |
lappend pat [expr {$N % $i}] | |
set N [expr {int($N/$i)}] | |
incr i | |
} | |
return $pat | |
} | |
# pat2perm pat : turn pattern returned by n2pat into | |
# permutation of integers. | |
proc pat2perm {args} { | |
if {[llength $args] == 1} { | |
set pat [lindex $args 0] | |
} else { | |
set pat $args | |
} | |
set source [list] | |
for {set i 0} {$i < [llength $pat]} {incr i} { | |
lappend source $i | |
} | |
set perm [list] | |
while {[llength $pat]} { | |
set i [lindex $pat end] | |
set pat [lrange $pat 0 end-1] | |
lappend perm [lindex $source $i] | |
set source [lreplace $source $i $i] | |
} | |
return $perm; | |
} | |
# n2perm N len : generate the $Nth permutation of $len objects | |
proc n2perm {N len} { | |
return [pat2perm [n2pat $N $len]] | |
} | |
proc main {} { | |
while {[gets stdin _] >= 0} { | |
set data [split $_] | |
set len [llength $data] | |
set numPermutations [::math::factorial $len] | |
for {set i 0} {$i < $numPermutations} {incr i} { | |
set permutation [list] | |
foreach {p} [n2perm $i [expr {$len - 1}]] { | |
lappend permutation [lindex $data $p] | |
} | |
puts $permutation | |
} | |
} | |
} | |
main | |
# @@PLEAC@@_5.0 | |
array set age { | |
Nat 24 | |
Jules 25 | |
Josh 17 | |
} | |
set age(Nat) 24 | |
set age(Jules) 25 | |
set age(Josh) 17 | |
array set foodColor { | |
Apple red | |
Banana yellow | |
Lemon yellow | |
Carrot orange | |
} | |
# @@PLEAC@@_5.1 | |
set array(foo) bar | |
# or | |
set key foo | |
set value bar | |
set array($key) $value | |
# or | |
array set array [list $key $value] | |
# foodColor defined per the introduction | |
set foodColor(Raspberry) pink | |
puts "Known foods:" | |
foreach food {[array names foodColor]} { | |
puts $food | |
} | |
# @@PLEAC@@_5.2 | |
if {[info exists array($key)]} { | |
# it exists | |
} else { | |
# it doesn't | |
} | |
# foodColor per the introduction | |
foreach name {Banana Martini} { | |
if {[info exists foodColor($name)]} { | |
puts "$name is a food." | |
} else { | |
puts "$name is a drink."; | |
} | |
} | |
array unset age | |
set age(Toddler) 3 | |
set age(Unborn) 0 | |
set age(Phantasm) false | |
foreach thing {Toddler Unborn Phantasm Relic} { | |
set result "$thing:" | |
if {[info exists age($thing)]} { | |
append result " Exists" | |
if {$age($thing)} { | |
append result " True" | |
} | |
if {$age($thing) != 0} { | |
append result " Non-zero" | |
} | |
} | |
puts $result | |
} ;# improved by Bob Techentin | |
# @@PLEAC@@_5.3 | |
# remove $KEY and its value from ARRAY | |
array unset ARRAY $KEY | |
# foodColor as per Introduction | |
proc print-foods {} { | |
variable foodColor | |
set foods [array names foodColor] | |
set food {} | |
puts "Keys: $foods" | |
puts -nonewline "Values: " | |
foreach food $foods { | |
set color $foodColor($food) | |
if {$color ne {}} { | |
puts -nonewline "$color " | |
} else { | |
puts -nonewline {(empty) } | |
} | |
} | |
puts {} | |
} | |
puts "Initially:" | |
print-foods | |
puts "\nWith Banana empty" | |
set foodColor(Banana) {} | |
print-foods | |
puts "\nWith Banana deleted" | |
array unset foodColor Banana | |
print-foods | |
# => Initially: | |
# => Keys: Banana Apple Carrot Lemon | |
# => Values: yellow red orange yellow | |
# => | |
# => With Banana empty | |
# => Keys: Banana Apple Carrot Lemon | |
# => Values: (empty) red orange yellow | |
# => | |
# => With Banana deleted | |
# => Keys: Apple Carrot Lemon | |
# => Values: red orange yellow | |
# several members can be deleted in one | |
# go if their names match a glob pattern, | |
# otherwise the [array unset] command must | |
# be called once for each name. | |
array unset foodColor ?a* | |
print-foods | |
# => Keys: Apple Lemon | |
# => Values: red yellow | |
# @@PLEAC@@_5.4 | |
foreach {key value} [array get ARRAY] { | |
# do something with $key and $value | |
} | |
# another way | |
set searchId [array startsearch ARRAY] | |
while {[set key [array nextelement ARRAY $searchId]] ne {}} { | |
set value $ARRAY($key) | |
# do something with $key and $value | |
} | |
foreach {food color} [array get foodColor] { | |
puts "$food is $color." | |
} | |
# => Banana is yellow. | |
# => Apple is red. | |
# => Carrot is orange. | |
# => Lemon is yellow. | |
set searchId [array startsearch foodColor] | |
while {[set food [array nextelement foodColor $searchId]] ne {}} { | |
set color $foodColor($food) | |
puts "$food is $color." | |
} | |
# => Banana is yellow. | |
# => Apple is red. | |
# => Carrot is orange. | |
# => Lemon is yellow. | |
# countfrom - count number of messages from each sender | |
if {[llength $argv] > 0} { | |
if {[catch {set f [open [lindex $argv 0]]} err]} { | |
error $err | |
} | |
} else { | |
set f stdin | |
} | |
while {[gets $f line] >= 0} { | |
if {[regexp {^From: (.*)} $line --> name]} { | |
if {[info exists from($name)]} { | |
incr from($name) | |
} else { | |
set from($name) 1 | |
} | |
} | |
} | |
if {[array size from] == 0} { | |
puts "No senders found" | |
exit | |
} | |
foreach person [lsort [array names from]] { | |
puts "$person: $from($person)" | |
} | |
# @@PLEAC@@_5.5 | |
# print each member of the array... | |
foreach {k v} [array get ARRAY] { | |
puts "$k => $v" | |
} | |
# ...or print all of it at once... | |
puts [array get ARRAY] | |
# ...or copy it to a list variable and print that... | |
set temp [array get ARRAY] | |
puts $temp | |
# ...or use the inspection command [parray] | |
parray ARRAY | |
# print with sorted keys | |
foreach {k} [lsort [array names ARRAY]] { | |
puts "$k => $ARRAY($k)" | |
} | |
# @@PLEAC@@_5.6 | |
# @@INCOMPLETE@@ | |
# @@INCOMPLETE@@ | |
# @@PLEAC@@_5.7 | |
# A list is a string in Tcl, so there is | |
# no problem storing multiple values as an | |
# array ("hash") item. | |
array set ttys [list] | |
set WHO [open "|who"] | |
while {[gets $WHO line] > -1} { | |
foreach {user tty} [split $line] break | |
lappend ttys($user) $tty | |
} | |
close $WHO | |
foreach user [lsort [array names ttys]] { | |
puts "$user: $ttys($user)" | |
} | |
# dummy code; there is no getpwuid command | |
foreach user [lsort [array names ttys]] { | |
puts "$user: [llength $ttys($user)] ttys." | |
foreach tty [lsort $ttys($user)] { | |
if {![catch {file stat /dev/$tty stat}]} { | |
set user [lindex [getpwuid $stat(uid)] 0] | |
} else { | |
set user "(not available)" | |
} | |
puts "\t$tty (owned by $user)" | |
} | |
} | |
proc multihash_delete {arrayname key value} { | |
upvar $arrayname array | |
set i {} | |
set len [llength $array($key)] | |
for {set i 0} {$i < $len} {incr i} { | |
if {[lindex $array($key) $i] eq $value} { | |
lset array($key) [lreplace $array($key) $i $i] | |
break | |
} | |
} | |
if {[llength $array($key)] <= 0} { | |
array unset array $key | |
} | |
} | |
# @@PLEAC@@_5.8 | |
#----------------------------- | |
package require struct 1.4 | |
array set REVERSE [::struct::list reverse [array get LOOKUP]] | |
#----------------------------- | |
# foodfind - find match for food or color | |
package require struct 1.4 | |
proc foodfind foodOrColor { | |
array set color { | |
Apple red | |
Banana yellow | |
Lemon yellow | |
Carrot orange | |
} | |
array set food [::struct::list reverse [array get color]] | |
if {[info exists color($foodOrColor)]} { | |
puts "$foodOrColor is a food with color $color($foodOrColor)." | |
} | |
if {[info exists food($foodOrColor)]} { | |
puts "$food($foodOrColor) is a food with color $foodOrColor." | |
} | |
} | |
foreach {f c} [array get color] { | |
lappend food($c) $f | |
} | |
puts "[join $food(yellow)] were yellow foods." | |
# @@PLEAC@@_5.9 | |
#----------------------------- | |
# a is the array to sort | |
set keys [lsort OPTIONS [array names a]] | |
foreach key $keys { | |
set value $a($key) | |
# do something with $key, $value | |
} | |
#----------------------------- | |
foreach food [lsort [array names foodColor]] { | |
puts "$food is $foodColor($food)." | |
} | |
#----------------------------- | |
proc sortFoods {a b} { | |
expr {[string length $a] - [string length $b]} | |
} | |
foreach food [lsort -command sortFoods [array names foodColor]] { | |
lappend foods $food | |
} | |
foreach food $foods { | |
puts "$food is $foodColor($food)." | |
} | |
#----------------------------- | |
# @@PLEAC@@_5.10 | |
#----------------------------- | |
array set merged [concat [array get A] [array get B]] | |
#----------------------------- | |
array unset merged | |
foreach {k v} [array get A] { | |
set merged($k) $v | |
} | |
foreach {k v} [array get B] { | |
set merged($k) $v | |
} | |
#----------------------------- | |
# foodColor as per the introduction | |
array set drinkColor { | |
Galliano yellow | |
"Mai Tai" blue | |
} | |
array set ingestedColor [concat [array get drinkColor] [array get foodColor]] | |
#----------------------------- | |
# foodColor per the introduction, then | |
array set drinkColor { | |
Galliano yellow | |
"Mai Tai" blue | |
} | |
array unset ingestedColor | |
foreach {k v} [array get foodColor] { | |
set ingestedColor($k) $v | |
} | |
foreach {k v} [array get drinkColor] { | |
set ingestedColor($k) $v | |
} | |
#----------------------------- | |
foreach substanceref {foodColor drinkColor} { | |
foreach {k v} [array get $substanceref] { | |
set substanceColor($k) $v | |
} | |
} | |
#----------------------------- | |
foreach substanceref {foodColor drinkColor} { | |
foreach {k v} [array get $substanceref] { | |
if {[info exists substanceColor($k)]} { | |
puts "Warning: $k seen twice. Using the first definition." | |
continue | |
} | |
set substanceColor($k) $v | |
} | |
} | |
#----------------------------- | |
# @@PLEAC@@_5.11 | |
#----------------------------- | |
set common {} | |
foreach k [array names arr1] { | |
if {[info exists arr2($k)]} { | |
lappend common $k | |
} | |
} | |
# common now contains common keys | |
#----------------------------- | |
set thisNotThat {} | |
foreach k [array names arr1] { | |
if {![info exists arr2($k)]} { | |
lappend thisNotThat $k | |
} | |
} | |
#----------------------------- | |
# foodColor per the introduction | |
# citrusColor is an array mapping citrus food name to its color. | |
array set citrusColor { | |
Lemon yellow | |
Orange orange | |
Lime green | |
} | |
# build up a list of non-citrus foods | |
set nonCitrus {} | |
foreach k [array names foodColor] { | |
if {![info exists citrusColor($k)]} { | |
lappend nonCitrus $k | |
} | |
} | |
#----------------------------- | |
#----------------------------- | |
# @@PLEAC@@_5.12 | |
#----------------------------- | |
# @@INCOMPLETE@@ | |
# @@INCOMPLETE@@ | |
#----------------------------- | |
# @@PLEAC@@_5.13 | |
#----------------------------- | |
# @@INCOMPLETE@@ | |
# @@INCOMPLETE@@ | |
#----------------------------- | |
# @@PLEAC@@_5.14 | |
#----------------------------- | |
array unset count | |
foreach element $LIST { | |
if {![info exists count($element)]} { | |
set count($element) 1 | |
} else { | |
incr count($element) | |
} | |
} | |
#----------------------------- | |
# @@PLEAC@@_5.15 | |
#----------------------------- | |
array set father { | |
Cain Adam | |
Abel Adam | |
Seth Adam | |
Enoch Cain | |
Irad Enoch | |
Mehujael Irad | |
Methusael Mehujael | |
Lamech Methusael | |
Jabal Lamech | |
Jubal Lamech | |
Tubalcain Lamech | |
Enos Seth | |
} | |
#----------------------------- | |
foreach name {Adam Tubalcain Elvis Enos} { | |
set fathers {} | |
while {[info exists father($name)]} { ;# if <name> has a father | |
lappend fathers $name ;# add it to the list | |
set name $father($name) ;# and check the father's father | |
} | |
puts $fathers | |
} | |
#----------------------------- | |
foreach {k v} [array get father] { | |
lappend children($v) $k | |
} | |
set sep {, } ;# separate output with commas | |
foreach name {Adam Tubalcain Elvis Lamech} { | |
if {[info exists children($name)] && [llength children($name)]} { | |
set res $children($name) | |
} else { | |
set res nobody | |
} | |
puts "$name begat [join $res $sep]" | |
} | |
#----------------------------- | |
foreach file $files { | |
if {[catch {open $file} F]} { | |
puts stderr "Couldn't read $file: $F; skipping." | |
continue | |
} | |
while {[gets $F line] >= 0} { | |
if {![regexp {^\s*#\s*include\s*<([^>]+)>} $line --> name]} { | |
continue | |
} | |
lappend includes($name) $file | |
} | |
close $F | |
} | |
#----------------------------- | |
set includeFree {} ;# list of files that don't include others | |
foreach k [array names includes] { | |
set uniq($k) {} | |
} | |
forech file [lsort [array names uniq]] { | |
if {![info exists includes($file)]} { | |
lappend includeFree $file | |
} | |
} | |
#----------------------------- | |
# @@PLEAC@@_5.16 | |
#----------------------------- | |
# @@INCOMPLETE@@ | |
# @@INCOMPLETE@@ | |
#----------------------------- | |
# @@PLEAC@@_6.0 | |
#----------------------------- | |
regexp $pattern $string | |
regsub $pattern $string $replacement | |
#----------------------------- | |
regexp sheep $meadow # True if $meadow contains "sheep" | |
#----------------------------- | |
regsub old $meadow new meadow # Replace "old" with "new" in $meadow | |
#----------------------------- | |
if [regexp -nocase {\bovines?\b} $meadow ] { | |
puts -nonewline {Here be sheep!} | |
} | |
#----------------------------- | |
set string {good food} | |
set string [regsub {o*} $string e] # regsub with out replacement var returns result. | |
#----------------------------- | |
foreach i [regexp -all -inline {\d+}] { | |
puts "Found number $i" | |
} | |
#----------------------------- | |
set numbers [regexp -all -inline {\d+}] | |
#----------------------------- | |
set digits 123456789 | |
set nonlap [regexp -inline -all {\d\d\d} $digits] | |
#no direct way for overlap since the regex behavior of /g|-all in tcl is differnt from perl. | |
set yeslap {} | |
for {set i 0} {$i < [string length $digits]} {incr i} { | |
set match [regexp -inline {\d\d\d} [string range $digits $i end]] | |
if {[string length $match]} { | |
lappend yeslap $match | |
} | |
} | |
#----------------------------- | |
# no direct pre and post match vars in tcl. | |
set string {And little lambs eat ivy} | |
regexp -indices -- {l.*s} $string idxs | |
set start [lindex $idxs 0] | |
set stop [lindex $idxs 1] | |
puts "([string range $string 0 $start-1]) ([string range $string $start $stop]) ([string range $string $stop+1 end])" | |
# @@PLEAC@@_6.1 | |
#----------------------------- | |
set dst $src | |
regsub this $dst that dst | |
#----------------------------- | |
regsub this $src that dst | |
#----------------------------- | |
# strip to basename | |
regsub ^.*/ $::argv0 {} progname | |
#----------------------------- | |
# it is easier to do it this way than the next. | |
package require struct::list | |
::struct::list map $words {string totitle} | |
# using regex. | |
set capword [gregsub {(\w+)} $words { | |
r {return [string totitle $r]} | |
}] | |
#----------------------------- | |
# /usr/man/man3/foo.1 changes to /usr/man/cat3/foo.1 | |
regsub {man(?=\d)} $manpage cat catpage | |
#----------------------------- | |
set bindirs {/usr/bin /bin /usr/local/bin} | |
set libdirs [string map {bin lib} $bindirs] | |
puts $libdirs | |
# /usr/lib /lib /usr/local/lib | |
#----------------------------- | |
regsub -all x $a y b # copy changed string to b | |
set b [regsub -all x $a y a] # change a, count goes in b | |
# @@PLEAC@@_6.2 | |
#----------------------------- | |
# matching letters | |
if [regexp {^[A-Za-z]+$} $var] { | |
#may be better to user [[:alpha:]]+$ | |
} | |
#----------------------------- | |
if [regexp {^[[:alpha:]]+$} $var] { | |
puts "var is purely alphabetic" | |
} | |
#----------------------------- | |
readlines $data { | |
{line} { | |
if {[regexp {^[[:alpha:]]+$} $line]} { | |
puts -nonewline "$line: alphabetic" | |
} else { | |
puts -nonewline "$line: line noice" | |
} | |
} | |
} | |
#__END__ | |
#silly | |
#façade | |
#coöperate | |
#niño | |
#Renée | |
#Molière | |
#hæmoglobin | |
#naïve | |
#tschüß | |
#random!stuff#here | |
# @@PLEAC@@_6.3 | |
#----------------------------- | |
# matching words | |
{\S+} # as many non-whitespace bytes as possible | |
{[A-Za-z'-]+} # as many letters apostrophes and hyphens | |
#----------------------------- | |
{\y([A-Za-z]+)\y} # usually best | |
{\s([A-Za-z]+)\s} # fails at ends or w/ punctuation | |
# @@PLEAC@@_6.4 | |
#----------------------------- | |
package require Tclx | |
set str {www.tcl.tk} | |
set re {(?x) # allow formatting | |
( # capture group | |
(?: # grouping parens | |
(?! [-_] ) # lookahead for neither - nor _ | |
[\w] + # hostname component | |
\. # add domain dot | |
)+ # repeat | |
[A-Za-z] # next must be letter | |
[\w-]+ # now trailing domain part | |
) | |
} | |
puts [gregsub $re $str { | |
{host} { | |
return "$host \[[host_info addresses $host]\]" | |
} | |
}] | |
#----------------------------- | |
set re {(?x) # replace | |
\# # a pound | |
(\w+) # varname | |
\# # another pound | |
} | |
puts [gregsub $re $str { | |
{var} { | |
return [uplevel 2 "set $var"] | |
} | |
}] | |
# @@PLEAC@@_6.5 | |
#----------------------------- | |
# finding Nth occurence of a match | |
set pond "One fish two fish red fish blue fish" | |
set want 3 | |
set count 0 | |
gregsub {(?i)(\w+)\s+fish} $pond { | |
{c} { | |
variable want | |
variable count | |
incr count | |
if {$want == $count} { | |
puts "The third fish is a $c one" | |
} | |
} | |
} | |
#----------------------------- | |
set fishes [regexp -all -inline -- {(?i)\w+(?=\s+fish)} $pond ] | |
puts "The third fish is a [lindex $fishes 2] one." | |
#----------------------------- | |
{(?i)(?:\w+\s+fish\s+){2}(\w+)\s+fish} | |
#----------------------------- | |
set count 0 | |
gregsub {(?i)(\w+)\s+fish} $pond { | |
{c} { | |
uplevel 2 {incr count} #or what eveer you want to do. | |
} | |
} | |
#----------------------------- | |
set count [regsub -all -- {PAT} $string {} {}] | |
#----------------------------- | |
set count [expr [llength [regexp -all -- {PAT} $string]] + 1] | |
#----------------------------- | |
# no overlapping matches. | |
# @@INCOMPLETE@@ | |
#----------------------------- | |
set colors [regexp -all -inline -- {(?i)\w+(?=\s+fish)} $pond ] | |
set color [lindex $colors 2] | |
# with out temporary. | |
set color [lindex [regexp -all -inline -- {(?i)\w+(?=\s+fish)} $pond ] 2] | |
puts "The third fish in the pond is $color" | |
#----------------------------- | |
set evens {} | |
foreach {a b} [regexp -all -inline -- {(?i)\w+(?=\s+fish)} $pond ] { | |
lappend evens $b | |
} | |
puts "The even numbered fish are $evens" | |
#----------------------------- | |
# hard to do sushi. | |
#----------------------------- | |
set pond "One fish two fish red fish blue fish swim here" | |
set color [lindex [regexp -all -inline -- {(?i)\w+(?=\s+fish)} $pond ] end] | |
puts "Last fish is $color" | |
# last fish is blue | |
#----------------------------- | |
set re {(?x) | |
A # find some pattern A | |
(?! # mustn't be able to find | |
.* # something | |
A # and A | |
) | |
$ # thru end of str | |
} | |
#----------------------------- | |
set pond "One fish two fish red fish blue fish swim here" | |
if [regexp -- {(?x) | |
\y(\w+)\s+fish\y | |
(?!.*\yfish\y) | |
} $pond all one] { | |
puts "Last fish is $one" | |
} else { | |
puts "Failed." | |
} | |
# last fish is blue | |
# @@PLEAC@@_6.6 | |
#----------------------------- | |
argf-iter { | |
line { | |
puts [regsub -all -- {<.*>} $line {}] | |
} | |
} | |
#----------------------------- | |
# headerfy: change certain chapter headers to html | |
set re {(?xn) | |
\A # start of record | |
( | |
^Chapter # title | |
\s+ | |
\d+ # decimal number | |
\s+ | |
: | |
.* | |
)$ | |
} | |
set options(CR) "\n" | |
argf-iter { | |
para { | |
variable re | |
puts -nonewline [regsub -all -- $re $para {<H1>\1</H1>}] | |
} | |
} | |
array unset options(CR) | |
#----------------------------- | |
set options(CR) "\n\n" | |
argf-iter { | |
para { | |
gregsub {(?w)^START(.*?)^END} $para { | |
{chunk} { | |
puts -nonewline "chunk in $::argv has $chunk" | |
} | |
} | |
} | |
} | |
# @@PLEAC@@_6.7 | |
#----------------------------- | |
# reading records with a pattern separator | |
set chunks [split [regsub -all -- {pattern} [read -nonewline $fd] "\0"] "\0"] | |
#----------------------------- | |
set chunks [split [regsub -all -- {(?n)^\.(Ch|Se|Ss)$} [read -nonewline $fd] "\0"] "\0"] | |
set len [llength $chunks] | |
puts "I read $len chunks" | |
# @@PLEAC@@_6.8 | |
#----------------------------- | |
# tcl does not have regexp range operators | |
#----------------------------- | |
set fd [open $argv] | |
set data [split [read $fd] "\n"] | |
regrange {BEGIN PATTERN} .. {ENDPATTERN} $data { | |
{line} { | |
puts ">$line" | |
} | |
} | |
set fd [open $argv] | |
set data [split [read $fd] "\n"] | |
foreach line [lrange $data $first_line_no $last_line_no-1] { | |
puts $line | |
} | |
#----------------------------- | |
set fd [open $argv] | |
set data [split [read $fd] "\n"] | |
regrange {BEGIN PATTERN} ... {ENDPATTERN} $data { | |
{line} { | |
puts ">$line" | |
} | |
} | |
set fd [open $argv] | |
set data [split [read $fd] "\n"] | |
foreach line [lrange $data $first_line_no-1 $last_line_no] { | |
puts $line | |
} | |
#----------------------------- | |
set fd [open $argv] | |
set data [split [read $fd] "\n"] | |
puts [lrange $data 15-1 17-1] # prints lines 15 .. 17 as it is indexed by 0. | |
#----------------------------- | |
# the perl logic is not directly portable due to absence of range operators. | |
set in_header {} | |
regrange {} .. {^$} $data { | |
{line} { | |
variable in_header | |
lappend in_header $line | |
} | |
} | |
set in_body {} | |
regrange {^$} .. {$-^} $data { # $-^ will not match any thing thus leaving an open end. | |
{line} { | |
variable in_body | |
lappend in_body $line | |
} | |
} | |
#----------------------------- | |
set fd [open $argv] | |
set data [split [read $fd] "\n"] | |
array set seen {} | |
regrange {(?i)^From:?\s} .. {^$} $data { | |
line { | |
variable seen | |
set ids [regexp -inline -all {[^<>(),;\s]+\@[^<>(),;\s]+} $line] | |
foreach id $ids { | |
if {![info exists seen($id)]} { | |
puts $id | |
set seen($id) 0 | |
} else { | |
incr seen($id) | |
} | |
} | |
} | |
} | |
# @@PLEAC@@_6.9 | |
#----------------------------- | |
proc glob2pat globstr { | |
# note - we dont need to do this, we already have 'glob' command. | |
# escapes the chars '\' '.' '+' '^' '$' '{' '}' '(' ')' | |
set patmap { | |
"\\" "\\\\" | |
{.} {\.} | |
{+} {\+} | |
{^} {\^} | |
{$} {\$} | |
"{" "\{" | |
"}" "\}" | |
{(} {\(} | |
{)} {\)} | |
* .* | |
? . | |
[ [ | |
] ] | |
} | |
# using a bre to avoid other regexp rules | |
return [append {} (?b)^ [string map $patmap [join $globstr]] $] | |
} | |
# @@PLEAC@@_6.10 | |
#----------------------------- | |
# tcl caches compiled regexp if it is assigned to a variable (and even if it is not, | |
# but that is restricted to last 30) so /o in perl is not necessary here. | |
set pattern {blue} | |
argf-iter { | |
line { | |
variable pattern | |
if [regexp -- $pattern $line] { | |
# do something. | |
} | |
} | |
} | |
#----------------------------- | |
set popstates {CO ON MI WI MN} | |
while {[gets $fd line] >= 0} { | |
foreach state $popstates { | |
if [regexp -- $state $line] { | |
puts $line | |
break | |
} | |
} | |
} | |
#----------------------------- | |
# using argf-iter | |
set popstates {CO ON MI WI MN} | |
argf-iter { | |
line { | |
variable popstates | |
foreach state $popstates { | |
if [regexp -- $state $line] { | |
puts -nonewline $line | |
break | |
} | |
} | |
} | |
} | |
#----------------------------- | |
set popstates {CO ON MI WI MN} | |
set pre {while {[gets $fd line]>= 0}} | |
set code {} | |
foreach state $popstates { | |
append code [subst -nocommands { | |
if [regexp -- $state [set line]] { | |
puts [set line] | |
} | |
}] | |
} | |
eval [lappend pre $code] | |
#----------------------------- | |
package require struct::list | |
set fd [open $argv] | |
set sw_pre {[switch -regexp {$line}} | |
set code {} | |
append code [::struct::list map $popstates {apply { | |
{state} { | |
return "$state {return 1}" | |
} | |
}}] | |
lappend code {default {return 0}} | |
set tmp {} | |
set myproc [append tmp $sw_pre { } [list [join $code]] {]}] | |
while {[gets $fd line] >= 0} { | |
if [subst [subst -nocommands $myproc]] { | |
puts $line | |
} | |
} | |
#----------------------------- | |
proc build_exp words { | |
# return a list of lambdas that can be applied to a line to get a | |
# result string containing matching results. | |
return [::struct::list map $words {apply | |
{{word} { | |
return "line {return \[regexp $word \$line\]}" | |
}} | |
}] | |
} | |
proc func {var f} { | |
return [apply $f $var] | |
} | |
proc + {a b} {return [expr ($a + $b)]} | |
proc * {a b} {return [expr ($a * $b)]} | |
proc build_match_func {func init words} { | |
#return an applicable lambda. | |
return "line {return \[::struct::list fold \[::struct::list map \[build_exp {$words}\] \[list func \$line\]\] $init $func\]}" | |
} | |
set match_any [build_match_func + 0 $words] | |
set match_all [build_match_func * 1 $words] | |
while {[gets $fd line] >= 0} { | |
if [apply $match_all $line] { | |
puts $line | |
} | |
} | |
#----------------------------- | |
# we cache all regex in tcl. so there is no difference here. | |
set popstates {CO ON MI WI MN} | |
while {[gets $fd line] >= 0} { | |
foreach state $popstates { | |
if [regexp -- $state $line] { | |
puts $line | |
break | |
} | |
} | |
} | |
# @@PLEAC@@_6.11 | |
#----------------------------- | |
chan configure stdout -buffering none | |
while {![eof stdin]} { | |
if {[catch { | |
puts -nonewline "Pattern? " | |
gets stdin pat | |
regexp $pat {} | |
} err]} { | |
puts "Invalid pattern" | |
} | |
} | |
#----------------------------- | |
proc is_valid_pattern pat { | |
return [expr ![catch {regexp $pat {}} err]] | |
} | |
#----------------------------- | |
set rexp [lindex $argv 0] | |
if [catch "regexp $rexp {}" err] { | |
puts "Bad Pattern $rexp: $::argv0" | |
exit -1 | |
} | |
set fd [open [lindex $argv 1]] | |
foreach para [split [regsub -all -- "\n\n" [read $fd] "\0"] "\0"] { | |
if [regexp $rexp $para] { | |
puts $para | |
} | |
} | |
close $fd | |
#----------------------------- | |
set safe [interp create -safe] | |
if [$safe eval {regexp $pat $line}] { | |
do_something | |
} | |
# @@PLEAC@@_6.12 | |
#----------------------------- | |
# @@INCOMPLETE@@ | |
# @@INCOMPLETE@@ | |
# @@PLEAC@@_6.13 | |
#----------------------------- | |
# @@INCOMPLETE@@ | |
# @@INCOMPLETE@@ | |
# @@PLEAC@@_6.14 | |
#----------------------------- | |
# perl does not support \G switch | |
# so we are left with: | |
gregsub {(\d+)} $str { | |
{match} { | |
puts "Found $match" | |
} | |
} | |
#----------------------------- | |
set n [gregsub {^( )} $str { | |
{match} { | |
return 0 | |
} | |
}] | |
#----------------------------- | |
gregsub {^,?(\d+)} $str { | |
{match} { | |
puts "Found number $match" | |
} | |
} | |
#----------------------------- | |
# tcl does not have /c modifier either. | |
proc gmatch {exp str block} { | |
set start 0 | |
while 1 { | |
if {[regexp -indices -start $start -- $exp $str idx]} { | |
set start [expr [lindex $idx 1] + 1] | |
apply $block [string range $str {expand}$idx] | |
} else break | |
} | |
return $start | |
} | |
set str "The year 1752 lost 10 days on the 3rd of September" | |
set e [gmatch {\d+} $str { | |
{match} { | |
puts $match | |
} | |
}] | |
if [regexp -indices -start $e -- {\S+} $str idx] { | |
puts "Found [string range $str {expand}$idx] after last number" | |
} | |
#----------------------------- | |
# use the [lindex $idx end] as the pos for next regexp match.. | |
# @@PLEAC@@_6.15 | |
#----------------------------- | |
# try removing tags very badly | |
regsub -all -- {<.*>} $line {} line | |
#----------------------------- | |
# non greedy but still bad. | |
regsub -all -- {<.*?>} $line {} line | |
#----------------------------- | |
# stil wrong | |
set txt "<b><i>this</i> and <i>that</i> are important</b> Oh, <b><i>me | |
too!</i></b>" | |
regexp -all -inline -- {(?x) <b><i>(.*?)</i></b> } $txt | |
#----------------------------- | |
{(?x)BEGIN((?:(?!BEGIN).)*)END} | |
#----------------------------- | |
{(?x) <b><i>( (?: (?!</b>|</i>). )* )</i></b> } | |
#----------------------------- | |
{(?x) <b><i>( (?: (?!</[bi]>). )* )</i></b> } | |
#----------------------------- | |
{(?x) | |
<b><i> | |
[^<]* #stuff not possibly bad and not possibly end | |
(?: | |
(?! </?[ib]> ) #what we cant have | |
< | |
[^>]* | |
) * | |
</i></b> | |
} | |
# @@PLEAC@@_6.16 | |
#----------------------------- | |
# no easy way to do this. | |
# a difference in the tcl regex implementation means that if I say \1+, it immediatly | |
# changes the definition of \1 do not know if this behavior is correct. | |
# but it means that unlike the perl version, we print the dup words multiple times. | |
# if they are repeated more than 2 times . | |
# using a non capturing gropu (?:xx\1xx) did not help. | |
set fd [open $argv] | |
set p 0 | |
foreach para [split [regsub -all -- "\n\n" [read -nonewline $fd] "\0"] "\0"] { | |
incr p | |
set start 0 | |
while 1 { | |
set re {\y(\S+)\y(\s+\1\y)} | |
if {[regexp -indices -start $start -- $re $para all one two]} { | |
puts "dup word '[string range $para {expand}$one]' at paragraph $p" | |
set start [expr [lindex $all end] + 1] | |
} else break | |
} | |
} | |
#----------------------------- | |
set a nobody | |
set b bodysnatcher | |
if [regexp -- {^(\w+)(\w+) \2(\w+)$} "$a $b" all 1 2 3] { | |
puts "$2 overlaps in $1-$2-$3" | |
} | |
#----------------------------- | |
{^(\w+?)(\w+) \2(\w+)$} | |
#----------------------------- | |
# prime factors | |
set arg 180 | |
set cap [string repeat o $arg] | |
while {[regexp -- {^(oo+?)\1+$} $cap all one]} { | |
puts -nonewline [string length $one] | |
regsub -all $one $cap o cap | |
} | |
puts [string length $cap] | |
#----------------------------- | |
# diophantine | |
set cap [string repeat o 281] | |
if {[regexp -- {(o*)\1{11}(o*)\2{14}(o*)\3{15}$} [string repeat o 281] | |
all 1 2 3]} { | |
puts "One solution is x=[string length $1] y=[string length $2] | |
z=[string length $3]" | |
} else { | |
puts "No match" | |
} | |
# One solution is x=17 y=3 z=2 | |
#----------------------------- | |
{^(o+)\1{11}(o+)\2{14}(o+)\3{15}$} => One solution is x=17 y=3 z=2 | |
{^(o*?)\1{11}(o*)\2{14}(o*)\3{15}$} => One solution is x=0 y=7 z=11 | |
{^(o+?)\1{11}(o*)\2{14}(o*)\3{15}$} => One solution is x=1 y=3 z=14 | |
# @@PLEAC@@_6.17 | |
#----------------------------- | |
# alpha | beta | |
{alpha|beta} | |
#----------------------------- | |
# alpha & beta | |
{(?=.*alpha)(?=.*beta)} | |
#----------------------------- | |
# alpha beta | beta alpha | |
{alpha.*beta|beta.*alpha} | |
#----------------------------- | |
# !beta | |
{^(?:(?!beta).)*$} | |
#----------------------------- | |
# !bad but good | |
{(?=(?:(?!BAD).)*$)GOOD} | |
# we dont have an operator like =~ or !~ in tcl so no prefered way. | |
#----------------------------- | |
if {[expr [regexp {pat1} $string] && [regexp {pat2} $string]]} { | |
something | |
} | |
#----------------------------- | |
if {[expr [regexp {pat1} $string] || [regexp {pat2} $string]]} { | |
something | |
} | |
#----------------------------- | |
# mini grep | |
set pat [::struct::list shift argv] | |
argf-iter { | |
line { | |
variable pat | |
if [regexp $pat $line] { | |
puts -nonewline $line | |
} | |
} | |
} | |
#----------------------------- | |
regexp {(?=.*bell)(?=.*lab)} "labelled" | |
[expr {[regexp {} bell] && [regexp {} lab]}] | |
#----------------------------- | |
if [regexp {(?xw) | |
^ # start | |
(?= # lookahead | |
.* | |
bell | |
) | |
(?= | |
.* | |
lab | |
) | |
} $murray_hill] { | |
puts "Looks like Bell Labs might be in Murray Hill!" | |
} | |
#----------------------------- | |
regexp {(?:^.*bell.*lab)|(?:^.*lab.*bell)} labelled | |
#----------------------------- | |
set brand labelled | |
if [regexp {(?xw) | |
(?: # non-capturing grouper | |
^ .*? # any amount of stuff at the front | |
bell # look for a bell | |
.*? # followed by any amount of anything | |
lab # look for a lab | |
) # end grouper | |
| # otherwise, try the other direction | |
(?: # non-capturing grouper | |
^ .*? # any amount of stuff at the front | |
lab # look for a lab | |
.*? # followed by any amount of anything | |
bell # followed by a bell | |
) # end grouper | |
} $brand] { | |
puts "Our brand has bell and lab separate." | |
} | |
#----------------------------- | |
regexp {(?w)^(?:(?!waldo).)*$} $map | |
#----------------------------- | |
if [regexp {(?xw) | |
^ # start of string | |
(?: # non-capturing grouper | |
(?! # look ahead negation | |
waldo # is he ahead of us now? | |
) # is so, the negation failed | |
. # any character (cuzza /s) | |
) * # repeat that grouping 0 or more | |
$ # through the end of the string | |
} $map] { | |
puts "There's no waldo here!" | |
} | |
#----------------------------- | |
{(?x) | |
^ # anchored to the start | |
(?! # zero-width look-ahead assertion | |
.* # any amount of anything (faster than .*?) | |
ttyp # the string you don't want to find | |
) # end look-ahead negation; rewind to start | |
.* # any amount of anything (faster than .*?) | |
tchrist # now try to find Tom | |
} | |
# @@PLEAC@@_6.18 | |
#----------------------------- | |
# @@INCOMPLETE@@ | |
# @@INCOMPLETE@@ | |
# @@PLEAC@@_6.19 | |
#----------------------------- | |
# @@INCOMPLETE@@ | |
# @@INCOMPLETE@@ | |
# @@PLEAC@@_6.20 | |
#----------------------------- | |
set ans [gets stdin] | |
set safe [interp create -safe] | |
if [$safe eval {regexp -nocase SEND $ans}] { | |
puts {Action is send} | |
} elseif [$safe eval {regexp -nocase STOP $ans}] { | |
puts {Action is stop} | |
} elseif [$safe eval {regexp -nocase START $ans}] { | |
puts {Action is start} | |
} elseif [$safe eval {regexp -nocase ABORT $ans}] { | |
puts {Action is abort} | |
} elseif [$safe eval {regexp -nocase LIST $ans}] { | |
puts {Action is list} | |
} elseif [$safe eval {regexp -nocase EDIT $ans}] { | |
puts {Action is edit} | |
} | |
#----------------------------- | |
set ans [gets stdin] | |
set safe [interp create -safe] | |
proc smatch {exp data} { | |
variable safe | |
return [$safe eval [list regexp -nocase $exp $data]] | |
} | |
set actions {SEND STOP START ABORT LIST EDIT} | |
foreach act $actions { | |
if [smatch $act $ans] { | |
puts "Action is [string tolower $act]" | |
} | |
} | |
#----------------------------- | |
set errors 0 | |
argf-iter { | |
cmd { | |
variable errors | |
switch -regexp $cmd { | |
edit invoke_editor | |
send deliver_message | |
list {$pager $file} | |
abort { | |
puts {see you} | |
exit | |
} | |
default { | |
puts "unknown command $cmd" | |
incr errors | |
} | |
} | |
} | |
} | |
# @@PLEAC@@_6.21 | |
#----------------------------- | |
set urls {(http|telnet|gopher|file|wais|ftp)} | |
set ltrs {\w} | |
set gunk {/#~:.?+=&%@!\-} | |
set punc {.:?\-} | |
set any "$ltrs$gunk$punc" | |
argf-iter { | |
line { | |
variable urls | |
variable ltrs | |
variable gunk | |
variable punc | |
variable any | |
#puts [subst -nocommands (?x) | |
regsub -all [subst -nocommands {(?x) | |
\\y | |
( | |
$urls : | |
[$any] +? | |
) | |
(?= | |
[$punc]* | |
[^$any] | |
| | |
$ | |
) | |
}] $line {<A HREF="\1">\1</A>} line | |
puts $line | |
} | |
} | |
# @@PLEAC@@_6.22 | |
#----------------------------- | |
# @@INCOMPLETE@@ | |
# @@INCOMPLETE@@ | |
# @@PLEAC@@_6.23 | |
#----------------------------- | |
{(?i)^m*(d?c{0,3}|c[dm])(l?x{0,3}|x[lc])(v?i{0,3}|i[vx])$} | |
#----------------------------- | |
regsub {(\S+)(\s+)(\S+)} $str {\3\2\1} str | |
#----------------------------- | |
{(\w+)\s*=\s*(.*)\s*$} # keyword is $1, value is $2 | |
#----------------------------- | |
{.{80,}} | |
#----------------------------- | |
{(\d+)/(\d+)/(\d+) (\d+):(\d+):(\d+)} | |
#----------------------------- | |
regsub -all {/usr/bin} $str {/usr/local/bin} str | |
#----------------------------- | |
gregsub {%([0-9A-Fa-f][0-9A-Fa-f])} $str { | |
{match} { | |
return [format %x $match] | |
} | |
} | |
#----------------------------- | |
regsub -all {(?x) | |
/\* # Match the opening delimiter | |
.*? # Match a minimal number of characters | |
\*/ # Match the closing delimiter | |
} $str {} | |
#----------------------------- | |
regsub {^\s+} $str {} str | |
regsub {\s+$} $str {} str | |
# but really, in Ruby we'd just do: | |
string trim $str | |
#----------------------------- | |
regsub -all {\\n} $str "\n" str | |
#----------------------------- | |
regsub -all {^.*::} $str {} str | |
#----------------------------- | |
{(?x)^([01]?\d\d|2[0-4]\d|25[0-5])\.([01]?\d\d|2[0-4]\d|25[0-5])\. | |
([01]?\d\d|2[0-4]\d|25[0-5])\.([01]?\d\d|2[0-4]\d|25[0-5])$} | |
#----------------------------- | |
regsub {.*$/|} $str {} str | |
#----------------------------- | |
set cols { } | |
if [info exists env::(TERMCAP)] { | |
set cols $::env(TERMCAP) | |
} | |
if [regexp {:co#(\d+):} $cols all one] { | |
set cols $one | |
} { | |
set cols 80 | |
} | |
#----------------------------- | |
set name [regsub -all { /\S+/|} "$::argv0 $argv" { }] | |
#----------------------------- | |
if {![regexp -nocase {linux} $tcl_platform(os)]} { | |
error "This isn't Linux" | |
} | |
#----------------------------- | |
regsub -all {\n\s+} $str {} str | |
#----------------------------- | |
regexp -all {\d+\.?\d*|\.\d+} $line nums | |
#----------------------------- | |
# @@INCOMPLETE@@ | |
# no direct translation for \W | |
# regexp -all {\y[^\Wa-z0-9_]+\y} $line capword | |
#----------------------------- | |
# regexp -all {\y[^\WA-Z0-9_]+\y} $line lowords | |
#----------------------------- | |
# regexp -all {\y[^\Wa-z0-9_][^\WA-Z0-9_]*\y} $line icwords | |
#----------------------------- | |
regexp -all {<A[^>]+?HREF\s*=\s*["']?([^'" >]+?)[ '"]?>} $line links | |
#----------------------------- | |
set initial {} | |
regexp {^\S+\s+(\S)\S*\s+\S} $line all initial | |
#----------------------------- | |
# @@INCOMPLETE@@ | |
#----------------------------- | |
set sentences {} | |
foreach para [split [regsub -all -- "\n\n" [read -nonewline $fd] "\0"] "\0"] { | |
regsub -all -- "\n" $para { } para | |
regsub -all -- { {3,}} $para { } para | |
lappend sentences [regexp -all -inline {\S.*?[!?.](?= |\Z)}] | |
} | |
#----------------------------- | |
{(\d{4})-(\d\d)-(\d\d)} # YYYY in $1, MM in $2, DD in $3 | |
#----------------------------- | |
# @@INCOMPLETE@@ | |
#----------------------------- | |
{(?i)\yoh\s+my\s+gh?o(d(dess(es)?|s?)|odness|sh)\y} | |
#----------------------------- | |
# @@INCOMPLETE@@ | |
# @@PLEAC@@_7.0 | |
#----------------------------- | |
set filename {/tmp/messages} | |
if {![catch {open $filename r} F]} { | |
while {[gets $F line] >= 0} { | |
if [regexp -- {blue} $line] { | |
puts $line | |
} | |
} | |
close $F | |
} else { | |
error "Opening $filename: $F" | |
} | |
#----------------------------- | |
while {[gets stdin line] >= 0} { #read from STDIN | |
if {![regexp -- {\d} $line]} { | |
puts stderr {No digit found} #writes to STDERR | |
} | |
puts "Read: $line" #writes to STDOUT | |
} | |
#----------------------------- | |
set logfile [open {/tmp/log} w] | |
close $logfile | |
#----------------------------- | |
puts $logfile {Countdown initiated} | |
puts {You have 30 seconds to reach minimum safety distance.} | |
# @@PLEAC@@_7.1 | |
#----------------------------- | |
# open file "path" for reading only | |
set source [open $path r] | |
# open file "path" for writing only | |
set sink [open $path w] | |
# open file "path" for reading only | |
set source [open $path RDONLY] | |
# open file "path" for writing only | |
set sink [open $path WRONLY] | |
# open "path" for reading and writing | |
set file [open $path r+ ] | |
# open "path" with the flags "flags" (see examples below for flags) | |
set file [open $path $flags] | |
# open file "path" read only | |
set file [open $path r] | |
set file [open $path RDONLY] | |
# open file "path" write only, create it if it does not exist | |
# truncate it to zero length if it exists | |
set file [open $path w] | |
set file [open $path {WRONLY TRUNC CREAT}] | |
# open file "path" write only, fails if file exists | |
set file [open $path {WRONLY EXCL CREAT}] | |
# open file "path" for appending | |
set file [open $path a] | |
set file [open $path {WRONLY APPEND CREAT}] | |
# open file "path" for appending only when file exists | |
set file [open $path {WRONLY APPEND}] | |
# open file "path" for reading and writing | |
set file [open $path r+] | |
set file [open $path w+] | |
set file [open $path RDWR] | |
# open file for reading and writing, create a new file if it does not exist | |
set file [open $path {RDWR CREAT}] | |
# open file "path" reading and writing, fails if file exists | |
set file [open $path {RDWR EXCL CREAT}] | |
# @@PLEAC@@_7.2 | |
#----------------------------- | |
# tcl open does not use chars with special meaning. | |
set file [open $filename] | |
# @@PLEAC@@_7.3 | |
#----------------------------- | |
set el [file split $path] | |
lset el 0 [glob [lindex $el 0]] | |
set expandedpath [file join {expand}$el] | |
# @@PLEAC@@_7.4 | |
#----------------------------- | |
# the raised exception contains the filename. | |
open afile r | |
# @@PLEAC@@_7.6 | |
#----------------------------- | |
set data { | |
Your data goes here | |
} | |
foreach line [split $data "\n"] { | |
# process the line | |
} | |
# @@PLEAC@@_7.6 | |
#----------------------------- | |
while {[gets stdin line] >= 0} { | |
# do something with the line. | |
} | |
#----------------------------- | |
foreach filename $argv { | |
# closing and exception handling are done by the block | |
if {![catch {open $filename r} F]} { | |
set line [gets $F] #do stuff with $line | |
close $F | |
} else { | |
error "can't open $filename" | |
} | |
} | |
#----------------------------- | |
if {![llength $argv]} { | |
set argv [glob {*.[Cch]}] | |
} | |
#----------------------------- | |
# arg demo 1 | |
set chop_first 0 | |
if {![string compare [lindex $argv 0] {-c}]} { | |
incr chop_first | |
set argv [lrange $argv 1 end] | |
} | |
#----------------------------- | |
# arg demo 2 | |
if [regexp -- {^-(\d+)$} [lindex $argv 0] all one] { | |
set columns $one | |
set argv [lrange $argv 1 end] | |
} | |
#----------------------------- | |
# arg demo 3 - clustered options | |
# unfortunately tcllib does not yet provide clustered opts | |
# so using the same logic as that of perl. | |
set append 0 | |
set ignore_ints 0 | |
set nostdout 0 | |
set unbuffer 0 | |
foreach arg $argv { | |
#process arg for -abcd | |
set parg $arg | |
while 1 { | |
if [regexp -- {^-(.)(.*)$} $parg all one two] { | |
switch -- $one { | |
{a} {incr append} | |
{i} {incr ignore_ints} | |
{n} {incr ignore_ints} | |
{u} {incr unbuffer} | |
default { error {usage: [-ainu] [filenames] ...}} | |
} | |
set parg -$two | |
} else { | |
break | |
} | |
} | |
} | |
#----------------------------- | |
set fd [open [lindex $argv 0]] | |
set data [read -nonewline $fd] | |
close $fd | |
#----------------------------- | |
foreach arg $argv { | |
set F [open $arg r] | |
for {set i 0} {[gets $F line] >= 0} {incr i} { | |
puts $arg:$i:$line | |
} | |
close $F | |
} | |
#----------------------------- | |
foreach arg $argv { | |
set F [open $arg r] | |
while {[gets $F line] >= 0} { | |
if [regexp -- {login} $line] { | |
puts $line | |
} | |
} | |
close $F | |
} | |
#----------------------------- | |
set fd [open [lindex $argv 0] r] | |
set data [read -nonewline $fd] | |
close $fd | |
set chunks 0 | |
foreach line [split $data "\n"] { | |
switch $line { | |
{^#} continue | |
{__(DATA|END)__} break | |
default { set chunks [llength $line]} | |
} | |
puts "Found $chunks chunks" | |
# @@PLEAC@@_7.8 | |
#----------------------------- | |
set old [open $old_file] | |
set new [open $new_file w] | |
while {[gets $old line] >= 0} { | |
# change $line, then... | |
puts $new $line | |
} | |
close $old | |
close $new | |
file rename $old_file "old.orig" | |
file rename $new_file $old_file | |
for {set i 0} {[gets $old line] >= 0} {incr i} { | |
if {$i == 20} { # we are at the 20th line | |
puts $new "Extra line 1" | |
puts $new "Extra line 2" | |
} | |
puts $new $line | |
} | |
for {set i 0} {[gets $old line] >= 0} {incr i} { | |
if {![expr (20 <= $i) && ($i <= 30)]} { | |
puts $new $line | |
} | |
} | |
# @@PLEAC@@_7.10 | |
#----------------------------- | |
set fd [open {itest} r+] | |
set data [read -nonewline $fd] | |
regsub {foo} $data {QQQ} data | |
chan seek $fd 0 | |
puts $fd $data | |
chan truncate $fd | |
close $fd | |
#----------------------------- | |
set fd [open {itest} r+] | |
set data [read -nonewline $fd] | |
regsub {foo} $data [clock format [clock seconds]] data | |
chan seek $fd 0 | |
puts $fd $data | |
chan truncate $fd | |
close $fd | |
# @@PLEAC@@_7.11 | |
#----------------------------- | |
# tcl does not yet support locking of files though it is available in tclx | |
# @@INCOMPLETE@@ | |
# @@INCOMPLETE@@ | |
# @@PLEAC@@_7.12 | |
#----------------------------- | |
chan configure $fd none | |
if [llength $argv] { | |
chan configure stdout none | |
} | |
puts -nonewline {Now you dont see it...} | |
sleep 2 | |
puts {Now you do} | |
#----------------------------- | |
# stderr is unbuffered by default. so this is not necessary | |
chan configure stderr none | |
chan configure $fd full | |
#----------------------------- | |
chan configure $sock none | |
chan configure $fd full | |
#----------------------------- | |
set sock [socket {www.tcl.tk} 80] | |
chan configure $sock -buffering none | |
puts $sock "GET / HTTP/1.0\n\n" | |
set resp [read -nonewline $sock] | |
close $sock | |
puts "DOC is \n$resp\n" | |
# @@PLEAC@@_7.13 | |
#----------------------------- | |
# assume fh1 fh2 fh3 are open channels | |
foreach $f {fh1 fh2 fh3} { | |
chan event [set $f] readable { | |
# do something when this becomes readable. | |
} | |
chan event [set $f] writable { | |
# do something when this becomes writable. | |
} | |
} | |
vwait forever | |
# @@PLEAC@@_7.14 | |
#----------------------------- | |
set fd [open {/dev/cua0} r+] | |
chan configure $fd -blocking 0 | |
#----------------------------- | |
set blocked [chan configure $fd -blocking] | |
chan configure $fd -blocking 0 | |
#----------------------------- | |
chan configure $fd -blocking 0 | |
chan puts $fd {some data} | |
if [chan blocked $fd] { | |
# incomplete write, but there is no case of | |
# us having to redo the write again since tcl | |
# does it in the back ground for us. | |
} | |
set buffer [chan read -nonewline $fd $bufsize] | |
if [chan blocked $fd] { | |
# did not read full bufsize. | |
} | |
# @@PLEAC@@_7.15 | |
#----------------------------- | |
# @@INCOMPLETE@@ | |
# @@INCOMPLETE@@ | |
# @@PLEAC@@_7.16 | |
#----------------------------- | |
# tcl filehandles are like any other vars | |
set fd [open {myfile}] | |
set newfd $fd | |
set data [myproc $newfd] | |
# @@PLEAC@@_7.17 | |
#----------------------------- | |
# @@INCOMPLETE@@ | |
# @@INCOMPLETE@@ | |
# @@PLEAC@@_7.18 | |
#----------------------------- | |
foreach $f {fh1 fh2 fh3} { | |
puts [set $f] $stuff_to_print | |
} | |
#----------------------------- | |
set fd [open {| tee file1 file2 > /dev/null} w] | |
puts $fd {data\n} | |
close $fd | |
# @@PLEAC@@_7.19 | |
#----------------------------- | |
# @@INCOMPLETE@@ | |
# @@INCOMPLETE@@ | |
# @@PLEAC@@_7.20 | |
#----------------------------- | |
# file descriptors are just like any other variables | |
set fd [open {file}] | |
set newfd $fd | |
# @@PLEAC@@_8.0 | |
#----------------------------- | |
foreach line [split [read -nonewline $fd] "\n"] { | |
puts [string length $line] # we get chomped line by default. | |
} | |
#----------------------------- | |
set lines [split [read -nonewline $fd] "\n"] | |
#----------------------------- | |
set data [read $fd] | |
#----------------------------- | |
# not a direct equivalent but it is not required in tcl. | |
puts $fd [list one two three] | |
#----------------------------- | |
puts {Baa baa black sheep.} | |
#----------------------------- | |
set buffer [read $fd 4096] | |
set rv [string length $buffer] | |
#----------------------------- | |
chan truncate $fd $length | |
# truncating with out a file handle is not possible directly in tcl. | |
#----------------------------- | |
set pos [chan tell $datafd] | |
puts "I am $pos bytes from the start of datafd" | |
#----------------------------- | |
chan seek $logfd 0 end | |
chan seek $datafd $pos start | |
chan seek $outfd -20 current | |
#----------------------------- | |
# in tcl, there is no partial write, as even in non blocking mode, | |
# tcl writes in the background to complete the write. | |
chan configure $datafd -blocking 0 -buffering none | |
puts -nonewline $mystring | |
chan configure $infd -blocking 0 -buffering none | |
set block [read $infd 256] | |
set len [string length $block] | |
expr {($len != 256) ? [puts "only read $len bytes"] : 0 } | |
#----------------------------- | |
set pos [seek $handle 0 current] #dont change position. | |
# @@PLEAC@@_8.1 | |
#----------------------------- | |
while {[gets $fd line] >= 0} { | |
while {[regexp -- {\\$} $line]} { | |
regsub -- {\\$} $line [gets $fd] line | |
} | |
#process the full $line here. | |
} | |
# @@PLEAC@@_8.2 | |
#----------------------------- | |
set count [wc -l $filename] | |
#----------------------------- | |
set fd [open $file] | |
set count [llength [split [read -nonewline $fd] "\n"]] | |
#----------------------------- | |
set count [expr [regsub -all -- "\n" [read -nonewline $fd] {} tmp] + 1] | |
#----------------------------- | |
for {set count 0} {[gets $fd line] > -1} {incr count} {} | |
#----------------------------- | |
# para is just \n\n | |
set count [expr [regsub -all -- "\n\n" [read -nonewline $fd] {} tmp] + 1] | |
# @@PLEAC@@_8.3 | |
#----------------------------- | |
while {[gets $fd line] >= 0} { | |
foreach word $line { | |
#do something with the word. | |
} | |
} | |
#----------------------------- | |
while {[gets $fd line] >= 0} { | |
foreach word [regexp -all -inline -- {\w[\w'-]*} $line] { | |
#do something with the word. | |
} | |
} | |
#----------------------------- | |
# word frequency | |
array set seen {} | |
while {[gets $fd line] >= 0} { | |
foreach word [regexp -all -inline -- {\w[\w'-]*} $line] { | |
incr seen([string tolower $word]) | |
} | |
} | |
#----------------------------- | |
array set seen {} | |
while {[gets $fd line] >= 0} { | |
incr seen([string tolower $line]) | |
} | |
set names [lsort -command {apply {{a b} {upvar seen seen; expr $seen($a) > $seen($b)}}} [array names seen]] | |
foreach line $names { | |
puts "$line $seen($line)" | |
} | |
# @@PLEAC@@_8.4 | |
#----------------------------- | |
package require struct::list | |
set lines [split [read -nonewline $fd] "\n"] | |
foreach line [struct::list reverse $lines] { | |
# do something with the line. | |
} | |
#----------------------------- | |
set lines [split [read -nonewline $fd] "\n"] | |
for {set i [llength $lines]} {$i} {incr i -1} { | |
set line [lindex $lines $i-1] | |
} | |
# same strategy for paragraphs. | |
# @@PLEAC@@_8.5 | |
#----------------------------- | |
while 1 { | |
myproc [read $fd] | |
while {[eof $fd]} { | |
after 5000 | |
seek $fd 0 current | |
} | |
} | |
#----------------------------- | |
set naptime 1000 | |
set fd [open {/tmp/logfile}] | |
while 1 { | |
set out [gets $fd] | |
if [string length $out] { | |
puts $out | |
} | |
while {[eof $fd]} { | |
after $naptime | |
seek $fd 0 current | |
} | |
} | |
#----------------------------- | |
file stat $logfile info | |
if {!$info(nlink)} { | |
exit 0 | |
} | |
# @@PLEAC@@_8.6 | |
#----------------------------- | |
set lines [split [read -nonewline $fd] "\n"] | |
set randline [lindex $lines [expr "round(rand() * [llength $lines])"]] | |
#----------------------------- | |
set fd [open {/usr/share/fortune/humorists}] | |
set lines [split [regsub -all -- {%\n} [read -nonewline $fd] "\0"] "\0"] | |
set idx [expr "round(rand() * [llength $lines])"] | |
puts [lindex $lines $idx] | |
# @@PLEAC@@_8.7 | |
#----------------------------- | |
set lines [split [read -nonewline $input] "\n"] | |
foreach line [shuffle $lines] { #assumes shuffle from chapt 4 | |
puts $output $line | |
} | |
# @@PLEAC@@_8.8 | |
#----------------------------- | |
for {set i 0} {[gets $fd line] >= 0} {incr i} { | |
if {$desired_line_number == $i} break | |
} | |
#----------------------------- | |
set lines [split [read -nonewline $input] "\n"] | |
set line [lindex $lines $desired_line_number] | |
#----------------------------- | |
proc build_index {data_file index_file} { | |
puts -nonewline $index_file [binary format i 0] | |
while {[gets $data_file line] >= 0} { | |
puts -nonewline $index_file [binary format i [tell $data_file]] | |
} | |
} | |
proc line_with_index {data_file index_file line_no} { | |
set size [string length [binary format i 0]] | |
set i_offset [expr $size * ($line_no - 1)] | |
seek $index_file $i_offset start | |
if {[tell $index_file] != $i_offset} { | |
error "Did not find $line_no" | |
} | |
set entry [read $index_file $size] | |
binary scan $entry i* d_offset | |
seek $data_file $d_offset start | |
if {[tell $data_file] != $d_offset} { | |
error "Did not find $line_no" | |
} | |
return [gets $data_file] | |
} | |
# usage | |
set dfd [open fortune.dat] | |
set ifd [open fortune.dat.index w] | |
build_index $dfd $ifd | |
close $dfd | |
close $ifd | |
set dfd [open fortune.dat] | |
set ifd [open fortune.dat.index] | |
puts [line_with_index $dfd $ifd 90] | |
close $dfd | |
close $ifd | |
#----------------------------- | |
# we dont have a tie. | |
package require struct::list | |
expr {([llength $argv] == 2) || [error "usage: print_line FILENAME | |
LINE_NUMBER\n"]} | |
::struct::list assign $argv filename linenumber | |
set fd [open $filename] | |
for {set i 0} {[gets $fd line] >= 0} {incr i} { | |
if {$linenumber == $i} { | |
puts $line | |
exit 0 | |
} | |
} | |
error "Didn't find line $line_number in $filename\n" | |
#----------------------------- | |
package require struct::list | |
expr {([llength $argv] == 2) || [error "usage: print_line FILENAME LINE_NUMBER\n"]} | |
::struct::list assign $argv filename linenumber | |
set dfd [open $filename] | |
set ifd [open $filename.index] | |
build_index $dfd $ifd | |
puts [line_with_index $dfd $ifd] | |
# @@PLEAC@@_8.9 | |
#----------------------------- | |
set fields [split [regsub -all -- {PATTERN} $record {\0}] "\0"] | |
#----------------------------- | |
set fields [split [regsub -all -- {:} $record {\0}] "\0"] | |
#----------------------------- | |
set fields [split [regsub -all -- {\s+} $record {\0}] "\0"] | |
#----------------------------- | |
set fields [split [regsub -all -- { } $record {\0}] "\0"] | |
# @@PLEAC@@_8.10 | |
#----------------------------- | |
set last 0 | |
while {[gets $fd line] >= 0} { | |
if {![eof $fd]} { | |
set last [tell $fd] | |
} | |
} | |
chan truncate $fd $last | |
# @@PLEAC@@_8.11 | |
#----------------------------- | |
chan configure $handle -translation binary | |
#----------------------------- | |
set gifname "picture.gif" | |
set gif [open $gifname] | |
chan configure $gif -translation binary | |
chan configure stdout -translation binary | |
while {![eof $gif]} { | |
puts -nonewline [read $gif [expr 8 * 2**10]] | |
} | |
# @@PLEAC@@_8.12 | |
#----------------------------- | |
set addr [expr $recsize * $recno] | |
seek $fh $addr start | |
set data [read $fh $recsize] | |
binary scan $buffer $format field1 field2 #we can not pass a list/array as argument. | |
#update fields | |
set buffer [binary format $format $field1 $field2] | |
seek $fh -$recsize current | |
puts -nonewline $fh $buffer | |
close $fh | |
#----------------------------- | |
# setting the login date. | |
# @@INCOMPLETE@@ | |
# @@PLEAC@@_8.13 | |
#----------------------------- | |
chan configure $fh -translation binary | |
seek $fh $addr start | |
set out [read $fh] | |
#----------------------------- | |
#not optimal. do not use it for large strings. | |
proc get_till_null {fh} { | |
set out {} | |
while {![eof $fh]} { | |
set char [read $fh 1] | |
set out "$out$char" | |
if [regexp -- {\0} $char] break | |
} | |
return $out | |
} | |
foreach addr $addrs { | |
#seek will detect the oct if it is prefixed with '0'. | |
seek $fh $addr start | |
puts [format {%x %o %d "%s"} $addr $addr $addr [get_till_null $fh]] | |
} | |
#----------------------------- | |
set fh [open $argv] | |
chan configure $fh -translation binary | |
foreach line [split [read $fh] "\0"] { | |
foreach word [regexp -all -inline -- {[\x20-\x7e]+} $line] { | |
puts $word | |
} | |
} | |
# @@PLEAC@@_8.14 | |
#----------------------------- | |
chan configure $fh -translation binary | |
while {![eof $fh]} { | |
set record [read $fh $recordsize] | |
binary scan $data $template field1 field2 field3 | |
} | |
# @@PLEAC@@_8.15 | |
#----------------------------- | |
array set user_preferences {} | |
foreach line [split [read -nonewline $fh] "\n"] { | |
regsub -- {#.*$} [string trim $line] {} line | |
if [string length $line] { | |
array set user_preferences [string map {= { }} $line] | |
} | |
} | |
# @@PLEAC@@_8.16 | |
#----------------------------- | |
file stat $filename statinfo | |
# statinfo now contains the relevant information. | |
foreach var [array names statinfo] { | |
set $var $statinfo($var) | |
} | |
set mode [expr $mode & 07777] | |
#----------------------------- | |
file stat $filename statinfo | |
if {!$statinfo(uid)) { | |
puts "Superuser owns $filename" | |
} | |
if {$statinfo(atime) > $statinfo(mtime)} { | |
puts "$filename has been read since it was written." | |
} | |
#----------------------------- | |
proc is_safe path { | |
file stat $path info | |
# owner neither su nor me | |
if {$info(uid) && ![file owned $path]} { | |
return 0 | |
} | |
# check if group or other can write file. | |
if {$info(mode) & 022} { | |
if {![file isdirectory $path]} { | |
return 0 | |
} | |
if {!($info(mode) & 01000)} { | |
return 0 | |
} | |
} | |
return 1 | |
} | |
#----------------------------- | |
proc is_verysafe path { | |
set build {} | |
foreach elem [file split $path] { | |
set build [file join $build $elem] | |
if {![is_safe $build]} { | |
return 0 | |
} | |
} | |
return 1 | |
} | |
# @@PLEAC@@_8.17 | |
#----------------------------- | |
#struct tmp { | |
# short ut_type; +2 -> s short | |
# +2 -> x2 padding | |
# pid_t ut_pid; +4 -> i int | |
# //alignment +20 -> x20 padding | |
# char ut_line[UT_LINESIZE]; +12 -> A12 char | |
# char ut_id[4]; +4 -> A4 char | |
# char ut_user[UT_NAMESIZE]; +32 -> A32 char | |
# char ut_host[UT_HOSTSIZE]; +256 -> A256 char | |
# struct exit_status ut_exit; +4 -> x4 skip | |
# | |
# long ut_session; +4 -> x4 skip | |
# struct timeval ut_tv; +8 -> ii int | |
# int32_t ut_addr_v6[4]; +16 -> iiii int | |
# char pad[20]; -> x20 skip | |
#}; | |
set typedef {s x2 i x20 A12 A4 A32 A256 x4 x4 ii iiii x20} | |
set sizeof [string length [binary format $typedef 0 0 {} {} {} {} 0 0 0 0 0 0 ]] | |
set wtmp [open {/var/log/wtmp}] | |
seek $wtmp 0 end | |
while 1 { | |
set buffer [read $wtmp $sizeof] | |
binary scan $buffer $typedef type pid line id user host tsec tmsec addr addr2 addr3 addr4 | |
scan $user %c ord | |
if {!$user || !$time || !$ord} continue | |
puts "type:$type user:$user uid:$line id:$id host:$host pid:$pid time:[clock format $tval1]" | |
puts [format "->%u.%u.%u.%u" [expr $addr & 0xff] [expr ($addr >> 8) & 0xff] [expr ($addr >> 16) & 0xff] [expr ($addr >> 24) & 0xff]] | |
} | |
# @@PLEAC@@_8.18 | |
#----------------------------- | |
# @@INCOMPLETE@@ | |
# @@INCOMPLETE@@ | |
# @@PLEAC@@_8.19 | |
#----------------------------- | |
# @@INCOMPLETE@@ | |
# @@INCOMPLETE@@ | |
#----------------------------- | |
# @@PLEAC@@_9.0 | |
# feat. Bob Techentin | |
#----------------------------- | |
if {[catch {file stat /usr/bin/vi entry} err]} {error "Couldn't stat /usr/bin/vi : $err"} | |
#----------------------------- | |
if {[catch {file stat /usr/bin entry} err]} {error "Couldn't stat /usr/bin : $err"} | |
#----------------------------- | |
# can't [file stat] a filehandle | |
#----------------------------- | |
file stat /usr/bin/vi inode | |
set ctime $inode(ctime) | |
set size $inode(size) | |
# or you can use specific [file] subcommands | |
set size [file size /usr/bin/vi] | |
#----------------------------- | |
# You have to read the file to test for binary data | |
if {![catch {open $filename r} F]} { | |
set data [read $F] | |
close $F | |
if {![string is ascii $data]} { | |
error "$filename doesn't have text in it." | |
} | |
} else { | |
error "Opening $filename: $F" | |
} | |
#----------------------------- | |
set files [glob /usr/bin/*] | |
foreach f $files { | |
puts "Inside /usr/bin is something called [file tail $f]" | |
} | |
#----------------------------- | |
# @@PLEAC@@_9.1 | |
# feat. Bob Techentin | |
#----------------------------- | |
set READTIME [file atime $filename] | |
set WRITETIME [file mtime $filename] | |
file atime $filename $NEWREADTIME | |
file mtime $filename $NEWWRITETIME | |
#----------------------------- | |
set atime [file atime $filename] | |
set mtime [file mtime $filename] | |
set atime [clock scan "- 1 week" -base $atime] | |
set mtime [clock scan "- 1 week" -base $mtime] | |
if {[catch { | |
file atime $filename $atime | |
file mtime $filename $mtime | |
} err]} { | |
error "couldn't backdate $filename by a week w/ file (a|m)time: $err" | |
} | |
#----------------------------- | |
file atime $file [clock seconds] | |
#----------------------------- | |
#!/bin/sh | |
# uvi - vi a file without changing its access times | |
# the next line restarts using tclsh \ | |
exec tclsh "$0" "$@" | |
if {[llength $argv] != 1} { | |
error "usage: uvi filename" | |
} | |
set file [lindex $argv 0] | |
file stat $file statinfo | |
if {[info exists env(EDITOR)]} { | |
exec $env(EDITOR) $file | |
} else { | |
exec vi $file | |
} | |
file atime $file $statinfo(atime) | |
file mtime $file $statinfo(mtime) | |
#----------------------------- | |
# @@PLEAC@@_9.2 | |
# feat. Bob Techentin | |
#----------------------------- | |
file delete $filename | |
eval file delete $filenames | |
#----------------------------- | |
if {[catch {file delete $file}]} { | |
error "Can't unlink $file" | |
} | |
#----------------------------- | |
# Tcl's [file delete] command doesn't return a count, | |
# so we have to count files before and after deletion. | |
set existing 0 | |
foreach f $filelist { | |
if {[file exists $f]} {incr existing} | |
} | |
catch {eval file delete $filelist} | |
set remaining 0 | |
foreach f $filelist { | |
if {[file exists $f]} {incr remaining} | |
} | |
if {$remaining > 0} { | |
set count [expr {$existing-$remaining}] | |
puts stderr "could only delete $count of $existing files" | |
} | |
#----------------------------- | |
# @@PLEAC@@_9.2 | |
# feat. Bob Techentin | |
#----------------------------- | |
file delete $filename | |
eval file delete $filenames | |
#----------------------------- | |
if {[catch {file delete $file}]} { | |
error "Can't unlink $file" | |
} | |
#----------------------------- | |
if {[catch {eval file delete $filelist}]} { | |
set remaining 0 | |
foreach f $filelist { | |
if {[file exists $f]} {incr remaining} | |
} | |
set count [expr {[llength $filelist] - $remaining}] | |
puts stderr "could only delete $count of $existing files" | |
} | |
# Tcl's [file delete] command doesn't return a count, | |
# so we have to count files before and after deletion. | |
set existing 0 | |
foreach f $filelist { | |
if {[file exists $f]} {incr existing} | |
} | |
catch {eval file delete $filelist} | |
set remaining 0 | |
foreach f $filelist { | |
if {[file exists $f]} {incr remaining} | |
} | |
if {$remaining > 0} { | |
set count [expr {$existing-$remaining}] | |
puts stderr "could only delete $count of $existing files" | |
} | |
#----------------------------- | |
# @@PLEAC@@_9.3 | |
# feat. Bob Techentin | |
#----------------------------- | |
file copy $oldfile $newfile | |
#----------------------------- | |
if {[catch {open $oldfile "r"} IN]} {error "can't open $oldfile: $IN"} | |
if {[catch {open $newfile "w"} OUT]} {error "can't open $newfile: $OUT"} | |
set blksize [fconfigure $IN -buffersize] | |
fcopy $IN $OUT -size $blksize | |
close $IN | |
close $OUT | |
#----------------------------- | |
# Tcl file operations are portable | |
file copy $oldfile $newfile | |
#----------------------------- | |
file copy datafile.dat datafile.bak | |
file rename -force datafile.new datafile.dat | |
#----------------------------- | |
# @@PLEAC@@_9.4 | |
# feat. Bob Techentin | |
#----------------------------- | |
unset seen | |
foreach filename $argv { | |
file stat $filename statinfo | |
set dev $statinfo(dev) | |
set ino $statinfo(ino) | |
if {![info exists seen($dev,$ino)]} { | |
# do something with $filename because | |
# we haven't seen it before | |
lappend seen($dev,$ino) $filename | |
# seen($dev,$ino) is a list of filenames for the same file | |
} | |
} | |
#----------------------------- | |
# @@PLEAC@@_9.5 | |
# feat. Bob Techentin | |
#----------------------------- | |
foreach file [glob [file join $dirname "*"]] { | |
# do something with $file | |
} | |
#----------------------------- | |
set dir /usr/local/bin | |
puts "Text files in $dir are:" | |
foreach file [glob [file join $dir "*"]] { | |
set fp [open $file "r"] | |
if {[string is ascii [read $fp]]} { | |
puts $file | |
} | |
close $fp | |
} | |
#----------------------------- | |
foreach file [glob [file join $dir "*"]] { | |
if {$file eq ".." || $file eq "."} continue | |
# ... | |
} | |
#----------------------------- | |
proc plainfiles {dir} { | |
set result [list] | |
foreach file [lsort [glob [file join $dir "*"]]] { | |
if {[string index [file tail $file] 0] eq "."} continue | |
if {[file type $file] eq "file"} { | |
lappend result $file | |
} | |
} | |
return $result | |
} | |
#----------------------------- | |
# @@PLEAC@@_9.6 | |
# List of regular files in current directory - file names only in list | |
set files [glob -nocomplain -type f -- *.c] | |
# ------------- | |
# As above, but with full path | |
set files [glob -directory [pwd] -nocomplain -type f -- *.c] | |
# ------------- | |
# As previous [which is more compact, so preferred], showing use of 'file' to build file names | |
set pwd [pwd] ; set files [glob -nocomplain -type f -- *.c] | |
# Assemble full path names from list entries | |
foreach f $files { | |
puts [file nativename [file join "$pwd" "$f"]] | |
} | |
# ------------- | |
# Variants of the *NIX, 'find', command from the 'fileutil' package | |
package require fileutil | |
# Set search path to current directory. Could have also have used either of ~, | |
# for the user's HOME directory, or a relative / absolute path | |
set path . | |
# ----- | |
# 1. Similar to, 'glob', but also allows 'regexp'-based globbing | |
set files [fileutil::findByPattern $path -glob -- *.c] | |
# ----- | |
# 2. Makes use of a 'filter' procedure | |
proc is_c {name} { return [string match *.c $name] } | |
set files [fileutil::find $path is_c] | |
# ----- | |
# In both cases: | |
# * Search is recursive | |
# * Full path names printed | |
foreach f $files { | |
puts $f | |
} | |
# ----------------------------- | |
# Two lists generated, first one filtered by file extension, second one by file type | |
package require fileutil | |
proc is_c_or_h {name} { return [string match -nocase *.\[ch\] $name] } | |
set path . ; set files [fileutil::find $path is_c_or_h] | |
foreach f $files { | |
if {[string match "text" [fileutil::fileType $f]]} { lappend textfiles $f } | |
} | |
foreach f $textfiles { | |
puts $f | |
} | |
# ------------- | |
# As above, but both file extension and file type considered in filter procedure, so | |
# only a single list is generated | |
package require fileutil | |
proc is_c_or_h_and_text {name} { | |
if {[string match -nocase *.\[ch\] $name] && ![catch {fileutil::fileType $name} filetype]} { | |
return [expr [string compare "text" $filetype] == 0] | |
} | |
return 0 | |
} | |
set path . ; set files [fileutil::find $path is_c_or_h_and_text] | |
foreach f $files { | |
puts $f | |
} | |
# ----------------------------- | |
# Sorted list of all subdirectories in the current directory which commence with the | |
# digits 0-9 | |
set dirs [lsort [glob -directory [pwd] -nocomplain -type d -- \[0-9\]*]] | |
# @@PLEAC@@_9.7 | |
# Minimal-code approach to this problem is to generate a list of paths using the | |
# 'find' or 'findByPattern' commands of the 'fileutil' package, then traverse that | |
# list processing each file in turn. A variation is to write a filter procedure for | |
# 'find' that processes each selected file whilst still retaining its expected | |
# behaviour. Whilst the latter is likely to be better-performing, it isn't generally | |
# recommended to have a filter procedure possess side-effecting behaviour | |
package require fileutil | |
# Conventional filter procedures for use with, fileutil::find | |
proc is_dir {name} { return [expr [fileutil::test $name {d}] != 0] } | |
# ----- | |
# Generate list of directories in a directory | |
set path . ; set files [fileutil::find $path is_dir] | |
# ------------- | |
# Side-effecting filter procedures | |
proc accum_filesize {name} { | |
global filesize | |
if [fileutil::test $name {f}] { set filesize [expr $filesize + [file size $name]] } | |
return 0 | |
} | |
proc biggest_file {name} { | |
global biggest | |
if {[fileutil::test $name {f}] && [file size $name] > $biggest} { | |
set biggest [file size $name] | |
} | |
return 0 | |
} | |
proc youngest_file {name} { | |
global youngest | |
if {[fileutil::test $name {f}] && [file mtime $name] < $youngest} { | |
set youngest [file mtime $name] | |
} | |
return 0 | |
} | |
# ----- | |
# Obtain total size of all files in a directory and its subdirectories | |
set path . ; set filesize 0 ; set files [fileutil::find $path accum_filesize] | |
puts $filesize | |
# Find biggest size file in a directory and its subdirectories | |
set path . ; set biggest 0 ; set files [fileutil::find $path biggest_file] | |
puts $biggest | |
# Find most recent file in a directory and its subdirectories | |
set youngest 2147483647 ; set files [fileutil::find $path youngest_file] | |
puts [clock format $youngest -format %D] | |
# Alternatively, one could implement a procedure that actually recurses through | |
# directories performing required processing. One approach would see the intermixing | |
# of recursing and processing code; another would see a generic recursing procedure | |
# passed the name of a processing procedure which is then applied to each selected | |
# file via the 'eval' command. The latter approach has the advanatage of being flexible, | |
# though performance is hampered due to the use of 'eval'; the former approach is more | |
# 'one-shot', but most likely, better performing | |
proc processDirectory {baseDir proc} { | |
set pwd [pwd] ; if [catch {cd $baseDir} result] { return } | |
foreach dir [glob -nocomplain -type d -- *] { | |
processDirectory $dir $proc | |
} | |
eval "$proc [pwd]" ; cd $pwd | |
} | |
# ------------- | |
proc show {dir} { puts $dir } | |
proc accum_filesize {dir} { | |
global filesize | |
foreach file [glob -directory $dir -nocomplain -type f -- *] { | |
set filesize [expr $filesize + [file size $file]] | |
} | |
} | |
proc biggest_file {dir} { | |
global biggest | |
foreach file [glob -directory $dir -nocomplain -type f -- *] { | |
if {[file size $file] > $biggest} { set biggest [file size $file]} | |
} | |
} | |
proc youngest_file {dir} { | |
global youngest | |
foreach file [glob -directory $dir -nocomplain -type f -- *] { | |
if {[file mtime $file] < $youngest} { set youngest [file mtime $file]} | |
} | |
} | |
# ------------- | |
# Obtain total size of all files in a directory and its subdirectories | |
set filesize 0 ; processDirectory [pwd] "accum_filesize" ; puts $filesize | |
# Find biggest size file in a directory and its subdirectories | |
set biggest 0 ; processDirectory [pwd] "biggest_file" ; puts $biggest | |
# Find most recent file in a directory and its subdirectories | |
set youngest 2147483647 ; processDirectory [pwd] "youngest_file" | |
puts [clock format $youngest -format %D] | |
# ----------------------------- | |
# Generate list of directories in a list of directories | |
if {$argc < 1} { puts stderr "usage: $argv0 dir..." ; exit 1 } | |
foreach dirname $argv { | |
processDirectory $dirname "show" | |
} | |
# @@PLEAC@@_9.8 | |
# The 'file delete' command can: | |
# * Delete both files and subdirectories | |
# * Recursively delete the latter | |
# Therefore, it is not necessary to construct a tree-traversing [recursive or otherwise] | |
# procedure in order to remove a directory tree. It may be, however, useful to use such | |
# an approach should it be necessary to implement special processing [e.g. interactive | |
# prompting]. The use of a custom procedure that intermixes recursing and processing code | |
# [as shown in the previous section] is probably the simplest, best performing approach, | |
# to this latter task. | |
# 'rmtree1' - straightforward implementation | |
if {$argc < 1} { puts stderr "usage: $argv0 dir..." ; exit 1 } | |
foreach dirname $argv { | |
if [catch {file delete -force -- $dirname} result] { | |
puts "Error deleting $dirname \[$result\]" | |
} | |
} | |
# ----------------------------- | |
# 'rmtree2' - recursive, tree-traversing implementation | |
# Recurser - recursively traverses directory tree | |
proc rmtree_ {baseDir} { | |
set pwd [pwd] ; if [catch {cd $baseDir} result] { return } | |
foreach dir [glob -nocomplain -type d -- *] { | |
rmtree_ $dir | |
} | |
# Let's delete the regular files in, 'baseDir' | |
foreach filename [glob -nocomplain -type f -- *] { | |
if [catch {file delete -force -- $filename} result] { | |
puts "Error deleting $filename \[$result\]" | |
} | |
} | |
# Let's move up, out of, 'baseDir', so as to allow it's deletion | |
cd $pwd | |
# Let's delete, 'baseDir' | |
set dirname [file join $pwd $baseDir] | |
if [catch {file delete -force -- $dirname} result] { | |
puts "Error deleting $dirname \[$result\]" | |
} | |
} | |
# ----- | |
# Launcher - performs validation, then starts recursive routine | |
proc rmtree {baseDir} { | |
if {![file exists $baseDir]} { | |
puts stderr "Directory does not exist" ; return | |
} | |
if [string match $baseDir* [pwd]] { | |
puts stderr "Cannot remove current directory or its parent" ; return | |
} | |
# Validation passed, so start recursing through subdirectories | |
return [rmtree_ $baseDir] | |
} | |
# ------------- | |
if {$argc < 1} { puts stderr "usage: $argv0 dir..." ; exit 1 } | |
foreach dirname $argv { | |
rmtree $dirname | |
} | |
# @@PLEAC@@_9.9 | |
set names [list x y z] | |
foreach file $names { | |
# This deliberately atempts to rename an existing file to it's own, same name, | |
# thus forcing an error [unless the -force option is used] | |
set newname file | |
# Error display mimics Perl example | |
## if [catch {file rename $file $newname} result] { | |
## puts stderr "Couldn't rename $file to $newname" | |
## } | |
# However, approach shown here is preferable as it furnishes more relevant | |
# diagnostic message(s) | |
if [catch {file rename $file $newname} result] { | |
puts stderr $result | |
} | |
} | |
# ----------------------------- | |
# | |
# A modified implementation of Larry's Filename Fixer. Rather than passing | |
# a single expression, a 'from' regexp is passed; each match in the file | |
# name(s) is changed to the value of 'to'. It otherwise behaves the same | |
# | |
if {$argc < 2} { puts stderr "usage: $argv0 from to \[files...\]" ; exit 1 } | |
set from [lrange $argv 0 0] ; set to [lrange $argv 1 1] | |
set argv [lrange $argv 2 [llength $argv]] | |
if {$argv == {}} { | |
while {[gets stdin line] >= 0} {lappend argv $line} | |
} | |
foreach f $argv { | |
set was $f ; regsub $from $f $to file | |
if {[string compare $was $file] != 0} { | |
if [catch {file rename $was $file} result] { | |
puts stderr $result | |
} | |
} | |
} | |
# @@PLEAC@@_9.10 | |
set path {/usr/lib/libc.a} | |
# ----- | |
set basename [file tail $path] | |
set dirname [file dirname $path] | |
# No equivalent to Perl's, 'fileparse', so must do: | |
set base [file tail $path] | |
set dir [file dirname $path] | |
set ext [file extension $path] | |
# ------------- | |
set path {/usr/lib/libc.a} | |
# ----- | |
set file [file tail $path] | |
set dir [file dirname $path] | |
puts "dir is $dir, file is $file" | |
# ----- | |
set name [file tail $path] | |
set dir [file dirname $path] | |
set ext [file extension $path] | |
puts "dir is $dir, name is $name, extension is $ext" | |
# ------------- | |
# According to the Tcl documentation, the 'file' command is platform-independant, so | |
# should correctly work for platforms such as MacOS. Code below assumes this, but is | |
# otherwise untested | |
set path {Hard%20Drive:System%20Folder:README.txt} | |
# ----- | |
set name [file tail $path] | |
set dir [file dirname $path] | |
set ext [file extension $path] | |
puts "dir is $dir, name is $name, extension is $ext" | |
# @@PLEAC@@_9.11 | |
# @@INCOMPLETE@@ | |
# @@INCOMPLETE@@ | |
# @@PLEAC@@_9.12 | |
# @@INCOMPLETE@@ | |
# @@INCOMPLETE@@ | |
# @@PLEAC@@_10.1 | |
# Subroutines in Tcl are created with the [proc] command, | |
# which takes a list of formal parameters as its second | |
# argument. | |
# On activation, the parameters are bound to the "words" | |
# of the call (which may contain data, variable/subroutine | |
# names, executable expressions, etc). This is a variant | |
# of call-by-name semantics. | |
proc hypotenuse {x y} { | |
return [expr {sqrt($x*$x+$y*$y)}] ;# Better still: use hypot() | |
} | |
set diag [hypotenuse 3 4] | |
# => 5.0 | |
# Subroutines may have a variable number of | |
# arguments, by using the special argument "args": | |
proc hypotenuse args { | |
foreach {x y} $args break | |
return [expr {hypot($x, $y)}] | |
} | |
# A subroutine can be applied to a list using [eval], | |
# which concatenates and then executes its arguments. | |
set a [list 3 4] | |
eval hypotenuse $a | |
# => 5.0 | |
# It is possible to create local references | |
# to variables in other stack frames using | |
# [upvar], so the typical idiom for | |
# pass-by-reference is to pass the variable's | |
# name as argument, and [upvar] it: | |
set nums [list 1.4 3.5 6.7] | |
proc trunc-em {name} { | |
upvar $name a | |
set len [llength $a] | |
for {set i 0} {$i < $len} {incr i} { | |
lset a $i [expr {int([lindex $a $i])}] | |
} | |
} | |
trunc-em nums | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment