Version 0 of glob forwards compatibility

Updated 2001-04-24 12:35:41

Tcl 8.3 introduces some new options to glob which generally make scripts more robust and cross-platform. For example, they help to avoid incorrect usage like:

 glob $dir/*
 glob [file join $dir *]

which both fail if $dir contains glob sensitive characters. The new syntax would be:

 glob -dir $dir *

which besides fixing the above problem has the added benefit of working on MacOS, since '/' is not assumed to be the directory separator.

If you want to write code using the new glob flags/options, such as glob -dir $dir -type d *, obviously your new code will no longer work with older versions of Tcl (e.g. 8.0-8.2). If you would like a compatibility layer which can be loaded into Tcl 8.0-8.2 (probably even older versions of Tcl too), overriding the old glob to emulate the new one in Tcl 8.3, here it is:

    if {[info tclversion] < 8.3} {
        ;proc getOpts {{take_value ""} {set "set"}} {
            upvar args a
            upvar opts o
            while {[string match \-* [set arg [lindex $a 0]]]} {
                set a [lreplace $a 0 0]
                if {$arg == "--"} {
                    return
                } else {
                    if {[set idx [lsearch -regexp $take_value \
                      "^-?[string range $arg 1 end]( .*)?$"]] == -1} {
                        set o($arg) 1
                    } else {
                        if {[llength [set the_arg \
                          [lindex $take_value $idx]]] == 1} {
                            $set o($arg) [lindex $a 0]
                            set a [lreplace $a 0 0]
                        } else {
                            set numargs [expr {[lindex $the_arg 1] -1}]
                            $set o($arg) [lrange $a 0 $numargs]
                            set a [lreplace $a 0 $numargs]
                        }
                    }
                }
            }
        }
        rename glob __glob
        ;proc glob {args} {
            getOpts {-t -types -type -dir -path}
            # place platform specific file separator in variable 'separator's
            regexp {Z(.)Z} [file join Z Z] "" separator
            if {[info exists opts(-join)]} {
                unset opts(-join)
                set args [list [eval file join $args]]
            }
            set add ""
            foreach t {type types} {
                if {[info exists opts(-$t)]} {
                    eval lappend opts(-t) $opts(-$t)
                    unset opts(-$t)
                }
            }
            if {[info exists opts(-t)]} {
                if {[set item [lsearch -exact $opts(-t) "d"]] != -1} {
                    set opts(-t) [lreplace $opts(-t) $item $item]
                    set add $separator
                    set isdir 1
                }
            }
            if {[set nocomplain [info exists opts(-nocomplain)]]} {
                unset opts(-nocomplain)
            }
            if {[info exists opts(-path)]} {
                if {[info exists opts(-dir)]} {
                    if {$nocomplain} {
                        return ""
                    } else {
                        error "Can't use option '-dir' with '-path'"
                    }
                }
                regsub -all {[][*?\{\}\\]} $opts(-path) {\\&} prefix
                unset opts(-path)
            } elseif {[info exists opts(-dir)]} {
                regsub -all {[][*?\{\}\\]} $opts(-dir) {\\&} prefix
                append prefix ${separator}
                unset opts(-dir)
            } else {
                set prefix ""
            }
            set res {}
            foreach arg $args {
                eval lappend res [__glob -nocomplain -- \
                  "${prefix}${arg}${add}"]
            }
            if {[info exists opts(-t)]} {
                # we ignore arguments to -t which haven't yet been handled,
                # since they are assumed to be platform specific
                unset opts(-t)
            }
            if {[set llen [llength [array names opts]]]} {
                set ok "-nocomplain, -join, -dir <dir>,\
                  -path <path>, -types <list of types>"
                if {$llen > 1} {
                    error "bad switches \"[array names opts]\":\
                      must be $ok or --"
                } else {
                    error "bad switch \"[array names opts]\":\
                      must be $ok or --"
                }
            } elseif {[llength $res]} {
                if {[info exists isdir]} {
                    foreach r $res {
                        lappend newres [string trimright $r $separator]
                    }
                    return $newres
                } else {
                    return $res
                }
            } elseif {$nocomplain} {
                return ""
            } else {
                switch -- [llength $args] {
                    0 {
                        error "wrong # args: should be \"glob ?switches?\
                          name ?name ...?\""
                    }
                    1 {
                        error "no files matched glob pattern \"$args\""
                    }
                    default {
                        error "no files matched glob patterns \"$args\""
                    }
                }
            }
        }
    }

--

I'm confused. Can you show some old code that breaks?

By the way, I assume you meant to say "if you want your OLD glob code to work ..."

Vince: hopefully the clarification above solves your confusion. The Tcl 8.3 changes to glob are 100% backwards compatible. The goal of this page is to provide forwards compatibility for people who write 'modern' code but still want those stuck at Tcl 8.0-8.2 to be able to use it. It handles most common uses of Tcl 8.3's glob.

This approach deserves its own page IMO: Forward compatibility --jcw