Version 5 of Unixy minitools

Updated 2008-03-28 15:16:51 by rvb

Richard Suchenwirth 2006-01-31 - The idea that two-letter commands are easier for the user was actually also present in Multics, but Unix popularized them to a much wider audience. Especially stylus-tapping on a PocketPC, rm is just more convenient that file delete.

The following set of procs and aliases all have well-known names, but don't expect the full features of their Unix counterparts - they're just "unixy", but pretty helpful already. Though I use them in Sepp, there's almost no dependencies either way (see at bottom). Feel free to use them, or to add more :^)

#-- This is just a bait for auto_index, because aliases aren't included there...

 proc unix {} {return "unixy toolset 0.1 loaded"}

 proc alias args {
   # Set an alias: alias foo = bar grill
   switch [llength $args] {
      0 {join [map docstring [interp aliases]] \n}
      1 {docstring $args}
      default {
         foreach {name eq} $args break
         if {$eq ne "="} {error [docstring alias]}
         eval [list interp alias {} $name {}] [lrange $args 2 end]
 alias ? = set ::errorInfo
 proc  -f name {expr {[file exists $name] && [file type $name] eq "file"}}
 alias -r  =  file readable
 alias -w  =  file writable
 alias -x  =  file executable
 alias -z  =  string eq ""

 proc  at {time body} {
   set dt [expr {[clock scan $time]-[clock sec]}]
   if {$dt<0} {incr dt 86400}
   after [expr {$dt*1000}] $body
 proc cat files {
    set res ""
    foreach file [eval glob $files] {
        set fp [open $file]
        append res [read $fp]
        close $fp
    set res
 alias cp = file copy -force
 proc  du {{directory .}} {
    set res 0
    foreach item [glob -nocomplain $directory/*] {
        switch -- [file type $item] {
            directory {incr res [du $item]}
            file {
                set res [expr {$res+([file size $item]+0)/1024}]
    set res
 proc date {{x ""}} {
     if {$x eq ""} {set x [clock seconds]}
     clock format $x -format %Y-%m-%d,%H:%M:%S
 proc echo {string {redirector -} {file -}} {
    set postcmd {close $fp}
    switch -- $redirector {
        >       {set fp [open $file w]}
        >>      {set fp [open $file a]}
        default {set fp stdout; set postcmd ""}
    puts $fp $string
    eval $postcmd
 proc  grep {re args} {
   foreach file [eval glob $args] {
        set fp [open $file]
        set n 0
        while {[gets $fp line] >=0} {
            incr n
            if [regexp $re $line] {puts "$file:$n $line"}
        close $fp
 proc  ll  args {
   if ![llength $args] {set args [glob *]}
   set res {}
   foreach f [lsort $args] {
      lappend res "[date [file mtime $f]]  [format %6d [file size $f]]  $f"
   join $res \n
 proc  ls  {{x .}} {lsort [glob -nocom -dir $x *]}
 alias mv = file rename
 alias rm = file delete
 proc  touch file {close [open $file a]}
 proc  wc file {
   # number of lines, words, characters in file
   set l 0; set w 0; set c 0
   set f [open $file]
   while {[gets $f line]>=0} {
      incr l
      incr w [llength [split $line]]
      incr c [string length $line]; incr c
   close $f
   list $l $w $c
 proc wc-l file {lindex [wc $file] 0}

#------------------- name=value assignment

 proc know what {proc unknown args $what\n[info body unknown]}

 know {if [regexp (.+)=(.+) [lindex $args 0] -> left right] {
          return [uplevel 1 [list set $left [lreplace $args 0 0 $right]]]

In alias, docstring is used, and map, which uses this simple implementation:

 proc map {f list} {
   set res {}
   foreach el $list {lappend res [$f $el]}
   set res

rvb I was looking here for an exec-less tcl tail, and found tailf (tail -f), but didn't find tail -n (n last lines). Please add any improvements or corrections!

 proc tail {args} {
     set count 10
     while {[llength $args] > 0} {
         set arg  [lindex $args 0]
         set args [lrange $args 1 end]
         switch -glob -- $arg {
           -*      {set count [expr -$arg]}
           default {set file $arg}
     set fd [open $file r]
     seek $fd -1 end
     set p [tell $fd]
     set result {}
     for {set i 0} {$i < $count} {incr i} {
         set line ""
         while {1} {
             incr p -1
             if {$p < 0} {break}
             seek $fd $p
             set c [read $fd 1]
             seek $fd $p
             if {[string match $c "\n"]} {
                 set result [linsert $result 0 $line]
             set line $c$line
     close $fd
     return [join $result "\n"]


 set file /etc/passwd
 puts [tail -5 $file]
 puts "tcl  [time {tail -10      $file} 100]"
 puts "exec [time {exec tail -10 $file} 100]"

See also Playing Bourne shell

Category Development | Category File | Arts and crafts of Tcl-Tk programming