Rich tclsh command line (python-like options)

sebres Inspired by python command line syntax (especially -c and -i option), I wrote this enhancement, as tcl-inject in init.tcl as well as C-coded in own branch (see sebres-rich-cmd-line ).

The syntax to check resp. execute some code in python:

  python -c 'print("hello world")'

And in tcl with this enhancement:

  tclsh -c 'puts "hello world"'

Compared to previous (original) syntax:

  echo 'puts "hello world"' | tclsh

Additionally it makes possible to get correct values of supplied parameters using variable ::argv and using parameter -i to enter interactive mode after execution (e. g. to debug or to work purposes).

New command line syntax

$ tclsh --help
tclsh ?-c command? ?-i? ?-e|-encoding name? ?fileName|--? ?arg arg ...?
tclsh ?-v|--version? ?-h|--help?
$ tclsh -v
8.6.8
$ tclsh -c 'puts ok; set a 5'
ok
5
$ tclsh -c 'puts ok; set a 5' -i
ok
% set a
5
% exit
$ tclsh -c 'set ::argv' -- 'a b' 'c d'
{a b} {c d}
$ tclsh -c 'puts $::argv' -- 'a b' 'c d'
{a b} {c d}
$ tclsh -c 'puts $::argv' /tmp/test123.tcl 'a b' 'c d'
{a b} {c d}
couldn't read file "/tmp/test123.tcl": no such file or directory
    while executing
"::source /tmp/test123.tcl"
$ tclsh -c 'puts $::argv' -i /tmp/test123.tcl 'a b' 'c d'
{a b} {c d}
couldn't read file "/tmp/test123.tcl": no such file or directory
    while executing
"::source /tmp/test123.tcl"
% file exists /tmp/test123.tcl
0
% exit

Backwards compatibility

It seems to be totally backwards compatible, because previously tclsh has anyway ignored the arguments starting with '-' (was not possible as filename) and simply entered interactive shell in this case.

How to apply

There are at least 3 variants how it may be applied

as binary (compiled)

as injection

  • Just append following script at the end of your init.tcl inside the tcl-library.

as simulation of tcl-library

  • Create own directory with init.tcl (with the wrapper to original init.tcl and content below) and set environment variable TCL_LIBRARY to this directory.
#======================================================
# Tcl-injection for rich tclsh command line
#  
#   https://wiki.tcl-lang.org/55434
#------------------------------------------------------
# Copyright (c) 2005- Serg G. Brester (aka sebres)
#======================================================
proc _try_enhanced_cmd {} {
    # avoid this enhancement if script invoked as executable:
    if {![info exists ::argv] || ![llength $::argv] || $::argv0 ne [info nameofexecutable] &&
        ($::tcl_platform(platform) ne "windows" || $::argv0 ne [file root [info nameofexecutable]])
    } {
        return
    }
    # parse args:
    set fileName {}
    set cmd {}
    set interact 0
    set enc {}
    while {[llength $::argv]} {
        set o [lindex $::argv 0]
        switch -- $o \
        "-c" {
            if {$cmd ne {} || [llength $::argv] < 2} {
                break
            }
            set cmd [lindex $::argv 1]
            set ::argv [lrange $::argv 2 end]
        } \
        "-i" {
            set interact 1
            set ::argv [lrange $::argv 1 end]
        } \
        "--" {
            set ::argv [lrange $::argv 1 end]
            break
        } \
        "-e" - "-encoding" {
            set enc [list -encoding [lindex $::argv 1]]
            set ::argv [lrange $::argv 2 end]
        } \
        "-v" - "--version" {
            return {
                ::puts "[info patchlevel]"
                exit 0
            }
        } \
        "-h" - "--help" {
            return {
                ::puts "[file tail $::argv0] ?-c command? ?-i? ?-e|-encoding name? ?fileName|--? ?arg arg ...?"
                ::puts "[file tail $::argv0] ?-v|--version? ?-h|--help?"
                exit 0
            }
        } \
        default {
            set fileName $o
            set ::argv [lrange $::argv 1 end]
            break
        }
    }
    set body {}
    if {$cmd ne {}} {
        append body $cmd
    }
    if {$fileName ne {}} {
        if {$cmd ne {}} { append body \n }
        append body [list ::source {*}$enc $fileName]
    }
    if {$body ne {}} {
        if {$interact} {
            set res {if {[catch $body]} {::puts stderr $::errorInfo}}
        } else {
            proc ::puts_ne {args} { catch { if {[lindex $args end] ne ""} {::puts {*}$args} } }
            set res {if {[catch {::puts_ne -nonewline [if 1 $body]}]} {::puts stderr $::errorInfo; ::exit 1} else {::exit 0}}
        }
        set body [string map [list \$body [list $body]] $res] 
    }
    return $body
}
# parse args and execute if expected:
if {[catch [_try_enhanced_cmd]]} {
    ::puts stderr $::errorInfo; ::exit 1
}
#======================================================

See Also

Branch sebres-rich-cmd-line in core.tcl-repository

category Argument Processing
Syntax parsing in Tcl
Tcl syntax
Argument Parsing, a discussion
Category Example

TIP 216 : Handling Command-Line Options in Tclsh and Wish