Version 26 of file join

Updated 2003-08-28 12:51:01

The [file join] command joins zero or more strings with the correct platform-dependent separators so that the result can be interpreted as a path name.


One useful application is to "normalize" a Windows pathname with backslashes to slashes, which is more robust in all kinds of intermediate processing, where substitution might occur:

 % file join {\foo\bar\grill}
 /foo/bar/grill

Back the other way with file nativename:

 % file nativename /foo/bar/grill
 \foo\bar\grill

DGP...but [file normalize] is a better tool for that purpose now.


One way to convert a file type from (possibly) relative to absolute is this:

  set filename [file join [pwd] $filename]

Note that you never need to use cd, and it is best if you don't except in response to user action. Otherwise you just confuse your users... :^/

DKF


MSW The most wonderful property about file join though is that it's so useless on lists that it hurts.

 puts [file join [file split a/b/c]]
 => a b c

I apologise for interjecting at this particular point, but what's wrong with the following?

 puts [eval file join [file split a/b/c]]
 => a/b/c

MSW It uses eval for list-splicing which is more than ugly (and evil :). Again, it's the asymmetry, imagine you'd have to use an evil for each of your join calls... *shudder* plus read on ... You could also say that tcl being too dumb to tell a list and a string with spaces apart is the real reason. Now that we're having objects, can't we tag lists as such please ?

 puts -nonewline "Enter a path for the savefile :"
 flush stdout
 set p [gets] ;# here you enter /home/[exec rm -f [glob -nocomplain ~]]/savefile
 # check if we're leaving our subtree, if not, store
 # a relative path only
 ... some file split, list checking, alter some early elements of the list ...
 eval file join ... ;# enjoy... I'll use [join] in the meantime.

I've been bitten by that more than once (e.g. when pruning away common parent-trees in paths, idea:

 set path a/b/c/d/e/f/g; set common_length 3
 set path [file join [lrange [file split $path] $common_length end]]
 => d e f g
 NOT d/e/f/g!!

...it just stinks. *cry* You end up using file split, join $path /, instead. Of course that means you're using native directory separators, which stinks nearly more ... but works. And honestly, I hate to use eval for list splicing :) Basically I don't want to splice the list, file join should just work!

RS It works as documented. You want a different behavior, that it joins the elements of a list. But this can lead to ambiguities, consider

 file join "C:/Program Files/Tcl"

which can be seen as a 2-element list... Also, you don't have to name the separator, Tcl knows:

 % join {foo bar grill} [file separator]
 foo\bar\grill

MSW True, I've stumbled over that so often though .. *mutter*

 % file separator
 bad option "separator": must be atime, attributes, channels, copy, delete, dirname,
 executable, exists, extension, isdirectory, isfile, join, lstat, mtime, mkdir, nativename, 
 owned, pathtype, readable, readlink, rename, rootname, size, split, stat, tail, type, 
 volumes, or writable

RS Well, yes, new feature, 8.4.2 has it. You can always fall back to

 proc file'separator {} {
    switch -- $::tcl_platform(platform) {
        unix    {return /}
        windows {return \\}
        default {error "unknown platform"}
    }
 }

MSW 8.4 is not an option for me (yet) as tix won't work with it, and all the CONSTifying shoots in my C-linkage heritage ... You don't like macintosh, eh ? :) But well, the ambigious list/string problem haunts us in tcl all the time... Even [list "a b"] ends up as a two element list (even though list clearly should create a one element list whose element can be interpreted as two element list ..). Ah well ...

Furthermore, I guess my real point didn't really get through. The file subcommands are not symmetric, and that stinks, no matter how well this misbehaviour is documented :)

 set x "a-b-c"
 set y [join [split $x -] -]
 if {$x != $y} then { error "braindead" }
 set x "a/b/c"
 set y [file join [file split $x]]
 if {$x == $y} then { error "miracle" }

Now imagine join would behave like file join ... yuck ...


Category Command ... well subcommand of file | Category Example