Version 5 of which

Updated 2004-03-20 20:39:34

AJB which - A utility that almost works like the Unix which, but it works in Windows as well. The difference: if it doesn't find the file, it just returns null. I did this because it makes it easier to work with in programs. Unix returns something like: which: no "filename" in /bin /usr/bin /usr/local/bin ... Also, if anyone has a Mac and knows what delimeter is used in ::env(PATH), could you please post the line to add to the opening switch statement so that this can be truly cross-platform. Thanks.

 proc which {filename} {
  switch $::tcl_platform(platform) {
    windows {set dirlist [split $::env(PATH) \;]}
    default {set dirlist [split $::env(PATH) :]}
  }
  foreach dir $dirlist {
    set fullname [file join $dir $filename]
    if {[file exists $fullname] && [file executable $fullname]} {
      return $fullname
    }
  }
  return ""
 }

The idea above could also be slightly improved with the ability to detect the .exe and .dll extensions in Windows, even if they're not provided in the argument:

 proc which {filename} {
  switch $::tcl_platform(platform) {
    windows {set dirlist [split $::env(PATH) \;]}
    default {set dirlist [split $::env(PATH) :]}
  }
  foreach dir $dirlist {
    set fullname [file join $dir $filename]
    if {[file exists $fullname] && [file executable $fullname]} {
      return $fullname
    }
    if {[file exists "$fullname.exe"] && [file executable "$fullname.exe"]} {
      return "$fullname.exe"
    }
    if {[file exists "$fullname.dll"] && [file executable "$fullname.dll"]} {
      return "$fullname.dll"
    }
  }
  return ""
 }

JH tkcon has a more elaborate which that goes beyond executables to also give info on internal Tcl stuff, extracted here:

 ## which - tells you where a command is found, requires what
 # ARGS:        cmd        - command name
 # Returns:        where command is found (internal / external / unknown)
 ## 
 proc which cmd {
     ## This tries to auto-load a command if not recognized
     set types [uplevel 1 [list what $cmd 1]]
     if {[llength $types]} {
         set out {}
         foreach type $types {
             switch -- $type {
                 alias                { set res "$cmd: aliased to [alias $cmd]" }
                 procedure        { set res "$cmd: procedure" }
                 command                { set res "$cmd: internal command" }
                 executable        { lappend out [auto_execok $cmd] }
                 variable        { lappend out "$cmd: $type" }
             }
             if {[info exists res]} {
                 global auto_index
                 if {[info exists auto_index($cmd)]} {
                     ## This tells you where the command MIGHT have come from -
                     ## not true if the command was redefined interactively or
                     ## existed before it had to be auto_loaded.  This is just
                     ## provided as a hint at where it MAY have come from
                     append res " ($auto_index($cmd))"
                 }
                 lappend out $res
                 unset res
             }
         }
         return [join $out \n]
     } else {
         return -code error "$cmd: command not found"
     }
 }

 ## what - tells you what a string is recognized as, used by which
 # ARGS:        str        - string to id
 # Returns:        id types of command as list
 ## 
 proc what {str {autoload 0}} {
     set types {}
     if {[llength [info commands $str]] || ($autoload && \
             [auto_load $str] && [llength [info commands $str]])} {
         if {[lsearch -exact [interp aliases] $str] > -1} {
             lappend types "alias"
         } elseif {
             [llength [info procs $str]] ||
             ([string match *::* $str] &&
             [llength [namespace eval [namespace qualifier $str] \
                     info procs [namespace tail $str]]])
         } {
             lappend types "procedure"
         } else {
             lappend types "command"
         }
     }
     if {[llength [uplevel 1 info vars $str]]} {
         upvar 1 $str var
         if {[array exists var]} {
             lappend types array variable
         } else {
             lappend types scalar variable
         }
     }
     if {[file isdirectory $str]} {
         lappend types "directory"
     }
     if {[file isfile $str]} {
         lappend types "file"
     }
     if {[llength [info commands winfo]] && [winfo exists $str]} {
         lappend types "widget"
     }
     if {[string compare {} [auto_execok $str]]} {
         lappend types "executable"
     }
     return $types
 }