tclenvironment-modules

A two-level symbolic link chasing function in Tcl


The upshot of what I'm trying to do is write a Tcl function that is equivalent to the following shell:

get_real_home () {
    dirname $(ls -l $(ls -l $(which "$1") | awk '{print $NF}') | awk '{print $NF'})
}

In short, this gives me the name of the directory that contains the actual binary that runs when I give it the name of a program managed by the Debian alternatives system by following a symlink, usually in /usr/bin, to another symlink in /etc/alternatives/, which points at the executable (or whatever) of the alternative currently in use. For example:

$ get_real_home java
/usr/lib/jvm/java-6-openjdk-amd64/jre/bin

The reason that I want to do this is that I am using Environment Modules, whose “native language” is Tcl, to manage the environment settings (chiefly PATH and LD_LIBRARY_PATH) for a number of compilers, interpreters, and libraries. This utility is pretty well a de facto standard on clusters.

For Java in particular (where there are many alternatives), it would be convenient to be able to set the environment (e.g. JAVA_HOME) to the right value for the current Debian alternative via an Environment Modules module that would “know” where the current Debian alternative points at. For that, the above symlink chaser is handy.

Of course, I could just stick what I already have (above) into a shell script and call that from Tcl in Environment Modules: a pragmatic if inelegant solution. I'd prefer the better “native” Tcl solution, but owing to my total ignorance of Tcl, I'm having difficulty doing it, despite it seeming like it should be trivial.

I'm sure this is trivial to someone who knows Tcl, but that isn't me :(


Solution

  • The file normalize command makes this virtually effortless.

    set javaBinDir [file dirname [file normalize {*}[auto_execok java]]]
    

    (The auto_execok command is a Tcl library procedure that does Gipsy Magic to work out how to run the given program. For the java program, it's equivalent to exec which; for shell builtins, it's trickier. It returns a list, a singleton in this case. I'm expanding it just in case you've got a directory with a space in the name, or some non-balanced braces. Unlikely…)


    If the target itself is a link, you need a little more work.

    set java [file normalize [lindex [auto_execok java] 0]]
    while {[file type $java] eq "link"} {
        # Ought to check for link loops...
        set java [file normalize [file join [file dirname $java] [file readlink $java]]]
    }
    puts "java really resolves to $java"
    

    file normalize won't do this for you automatically, as you might be wanting to refer to the link itself and not the thing it refers to. Fortunately, file join does The Right Thing when presented with both relative and absolute components; this appears to work when I try it in a (simulated) example.