'''`[file] join`''' forms a file path from one or more path components. `file join`, not `[file normalize]`, is the the standard way to transform a possible relative path into an absolute path. ** See Also ** * [file]: * [file separator]: * [file split]: * [Tilde Substitution]: ** Synopsis ** : '''file join''' ''name ?name ...?'' ** Documentation ** [http://www.tcl-lang.org/man/tcl/TclCmd/file.htm%|%official reference]: ** Description ** '''`file join`''' joins one or more strings into a file path using the correct platform-dependent separators. If a ''name'' is a relative path component, then it is joined to the previous file name argument. If a ''name'' is an absolute path, all previous arguments are discarded and any subsequent arguments are then joined to it. For example, ====== file join a b /foo bar ====== returns ======none /foo/bar ====== Any of the names can contain separators, and the result is always canonical for the current platform: '''`/`''' for Unix and Windows, and '''`:`''' for pre-OSX Macintosh. `file join` helps to avoid redundant separator characters: ====== set prefix / file join $prefix another directory ;# -> /another/directory lindex $prefix/another/directory ;# -> //another/directory ====== To join path elements contained in a [list], use `[{*}]`: ====== set path [list a b c] file join {*}$path ====== Another example: ====== set path [list a b c d e f g] set prefix 3 set path [file join {*}[lrange $path $prefix end]] ====== Or, for historic versions of Tcl without the `[{*}]` operator: ====== set path [list a b c d e f g] set prefix 3 set cmd [concat {file join} [lrange $path $prefix end]] set path [eval $cmd] puts $path ====== If `file join` joined list elements, it's behaviour would be incorrect in the following case: ====== #warning: bad code ahead! file join {C:/Program Files/Tcl} ====== On [Microsoft Windows%|%Windows], a UNC network path is treated similar to a drive specification: ====== % file join //host/location file //host/location/file ====== A disconnected host specification is not a UNC path, and file join treats it as a file location: ====== % file join //host location file /host/location/file ====== On Unix, a leading // is removed: ====== % file join //host/location file /host/location/file ====== ** The `[pwd]` Trick ** The standard method in Tcl for transform a possibly relative path to an absolute path is: ====== set filename [file join [pwd] $filename] ====== [DKF] [PYK]: `[cd]` can introduces an environment state that is easy to lose track of. It is good practice to instead use `[pwd]` to form absolute paths to work with. ** Gotcha: Fully-qualified Names and any Previous Names ** Any ''name'' arguments prior to the last fully-qualified names are dropped: ====== set newpath [file join lib /usr] ;# -> /usr ====== This is useful when the goal is to convert an unqualified name to a particular fully-qualified name, for example, to allow the user to provide a relative or a fully-qualified file name: ====== set newpath [file join [pwd] $filename] ====== When ''$b'' is relative, the current directory will be prepended to it, but when ''$b'' is absolute, it isn't changed. While useful in the case mentioned above, it can be surprising to discover that file paths are not joined as expected because some path is already fully-qualified. '''References:''' [http://groups.google.com/d/msg/comp.lang.tcl/VPTNjEfqpAY/QIXn1lJUvakJ%|%file join behaviour and ye olde tilde fiasco] ,[comp.lang.tcl] ,2009-06-26: ** Using `file join` to Normalize Path Separators ** `file join` can be used to "normalize" the path separators for relative paths that you don't want to subject to the full `[file normalize]`: ======none % file join {\foo\bar\grill} /foo/bar/grill ====== Back the other way with ''file nativename'': ====== % file nativename /foo/bar/grill \foo\bar\grill ====== ** Escaping Tilde Substitution ** To join a file whose name begins with "~" (tilde), prefix it with "./": ====== file join pwd ./~filename.ext ====== See [Tilde Substitution] ** Difference Between 8.6 and 8.7 ** [AMG]: In Tcl 8.6.8, [[file join //a/b]] returns `//a/b`, but in Tcl 8.7a1, [[file join //a/b]] returns `/a/b`. This got me in trouble because I was trying to work with Windows UNC paths. In the end I just had to concatenate strings and forgo [[file join]]. [[[file nativename]]] worked right, at least. [bll] 2018-7-11: Seems like a bug to me. A valid path should not be mangled. [bll] 2018-7-27: See [https://core.tcl.tk/tcl/tktview/baf43873720f5e92ae1142b1fc8d343b3648b54d%|%file join trashes valid network paths]. ** Difference Between Linux and Windows ** [AMG]: In Tcl 8.6.8 on Windows, [[file join C: foobar]] returns `C:foobar`, whereas on Linux it returns `C:/foobar`. The latter is obvious, but the former is a special case due to volume-relative paths which are a Windows-only feature. In order to get Linux-like behavior, I had to specially check for top-level path components named X: and append / before passing to [[file join]]. On both Windows and Linux, [[file join C:/ foobar]] gives `C:/foobar` which is what I needed all along. ** Semi-Normalize a Path ** [bll] 2018-7-11: I define semi-normalize as normalizing the path separators and removing any /../, but not following symlinks (e.g. /var/tmp on Mac OS X gets normalized as /private/var/tmp, which is not really wanted). See also [https://core.tcl.tk/tcllib/doc/trunk/embedded/www/tcllib/files/modules/fileutil/fileutil.html#1%|%fileutil::lexnormalize] <> Tcl syntax help | Command | Example | File | gotcha