Unixy minitools

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]
                 break
             }
             set line $c$line
         }
     }
     close $fd
     return [join $result "\n"]
 }

Example:

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



 29mar2011 gavino
 a tcl vmstat inspired by dastat which a clone of would be ultimate goal
 new-host$ ./tclstat3.tcl
 r b w freememG pagin-out cpu-us-sy-id contextswitches
 4 0 0 1.90     0 0       11 3 86       701
 0 0 0 1.90     0 0       2 2 96       672
 1 0 0 1.90     0 0       2 0 97       815
 0 0 0 1.90     0 0       6 2 91       741
 ^C
 new-host$ cat ./tclstat3.tcl
 #!/apps/tcl/bin/tclsh8.6
 puts "r b w freememG pagin-out cpu-us-sy-id contextswitches"
 while {1} {
 set raw [exec vmstat -c 3]
 set rawsplit [split $raw "\n"]
 set raw5 [lindex $rawsplit 4]
 set proc [lrange $raw5 0 2]
 set freemem [expr [lindex $raw5 4] / 1048576.0]
 set nicemem [format %.2f $freemem]
 set pageinout [lrange $raw5 7 8]
 set ussysid [lrange $raw5 16 18]
 set cswitch [lindex $raw5 15]
 puts "$proc $nicemem     $pageinout       $ussysid       $cswitch"
 }

See also Playing Bourne shell