Version 11 of Playing Bourne shell

Updated 2002-02-01 13:21:24

Richard Suchenwirth will have to exercise Tcl with colleagues who are familiar with the Bourne shell (/bin/sh - see Tcl heritage for the influences it had on Tcl!), and one task will be to replace /bin/sh scripts with equivalent Tcl scripts.

To make this migration easier, I am planning to introduce some syntactic sugar (Salt and sugar), so familiar built-ins can still be used. Most simply with interp alias:

 interp alias {} echo {} puts stdout
 interp alias {} read {} gets stdin ;# if we don't need the real [read]
 # interp alias {} whoami {} subst $::tcl_platform(user)
 # raises cryptic error message when called with args, hence:
 proc whoami {} {set ::tcl_platform(user)} 

 interp alias {} -r   {} file readable
 interp alias {} -w   {} file writable
 interp alias {} -x   {} file executable

Note however that echo's output cannot be redirected to a file, or through a pipe. Things like

 foo=`echo $bar | grep "^grill"`

would have to be restructured:

 set foo [exec grep ^grill <<$bar]

which also has its charms, but needs habituation. (On the other hand, a Tcl'er would prefer regexp anyway...)


The following one-liner emulates a frequently used part of /bin/sh's test command:

 proc -f name {expr {[file exists $name] && [file type $name]=="file"}}
 proc -z string {expr {[string length $string]==0}}

This way, /bin/sh code written like this:

 if [ -f $filename ] ...

needs no rewriting - but be aware that this is not a complete emulation of tests switches, so

 if [ $# -ne 1 ] ...

would have to rewritten in expr syntax (braced condition, C operators), but we even emulate the Perlish special variable ?# :

 set # [llength $argv]
 if {${#} != 1 } ...

This fills most of the shell special variables:

 # Create some shellish variables:
 proc sh'specials {} {
    global argv
    set ::0   $::argv0
    set n 0
    foreach i $argv {set ::[incr n] $i}
    set ::# [llength $argv]
    set ::\$ [pid]
    set ::? 0 ;# exit status of last command
 }

The following emulates the behavior that errors are not propagated, but may be detected from the special $? variable:

 proc ! args {
    if [catch {eval exec $args} res] {
        if {[lindex $::errorCode 0]=="CHILDSTATUS"} {
            set ::? [lindex $::errorCode 2]
        }
    }
    set res
 }
 proc !! args {puts $args; puts [eval ! $args]}

This wraps the required clock calls into a lookalike of UNIX's date command:

 proc date {args} {
    set cmd {clock format [clock sec]}
    foreach arg $args {
        if {$arg=="-u"} {
            append cmd " -gmt 1"
        } elseif [regexp {\+(.+)} $arg -> fmt] {
            append cmd " -format [list $fmt]"
        } else {error "usage: date ?-u? ?+%H%M%S...?"}
    }
    eval $cmd
 }

To tell the truth, this is not a Bourne shell issue, but in sh scripts, system executables are often called which may not be present (or with different behavior) on other platforms.


Tcl and other languages - Arts and crafts of Tcl-Tk programming