file join, a subcommand of
file, joins together elements to form a file path. Somewhat counterintuitively,
file join, not
file normalize, is the the standard
Tcl way to transform a possible relative path into an absolute path.
Synopsis edit
-
- file join name ?name ...?
Documentation edit
-
- http://www.tcl.tk/man/tcl/TclCmd/file.htm
Description edit
file join joins zero or more strings using the correct platform-dependent separators so that the result can be interpreted as a path name. If a particular
name is relative, then it will be joined to the previous file name argument. Any argument that is an absolute path causes all previous arguments to be discarded, and any additional arguments will then be joined to it. For example,
file join a b /foo bar
returns
/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 does not join items in lists. If a list contains path elements to be joined, use
Argument expansion:
file join {*}[file split a/b/c]
If the
{*} operator is not available in your version of Tcl, and it isn't feasible to upgrade, see
[eval] for the proper way to expand a list.
Another example:
set path a/b/c/d/e/f/g; set common_length 3
set path [file join {*}[lrange [file split $path] $common_length end]]
Or, for historic versions of Tcl without the
{*} operator:
set path a/b/c/d/e/f/g; set common_length 3
set script {file join}
set script [concat $script [lrange [file split $path] $common_length end]]
set path [eval $script]
puts $path
One reason
file join doesn't join list elements is that its behaviour would then be incorrect in the following case:
#warning: bad code ahead!
file join "C:/Program Files/Tcl"
On 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 will treat 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
See Also edit
The standard method in Tcl for transform a possibly relative path to an absolute path is:
set filename [file join [pwd] $filename]
DKF: 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... :^/
Gotcha: Fully-qualified Names and any Previous Names edit
Any
names 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:
[file join behaviour and ye olde tilde fiasco] ,[comp.lang.tcl] ,2009-06-26
Using [file join] to Normalize Path Separators edit
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:
% 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 edit
To join a file whose name begins with "~" (tilde), prefix it with "./":
file join pwd ./~filename.ext
See
Tilde SubstitutionDifference Between 8.6 and 8.7 edit
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/baf43873720f5e92ae1142b1fc8d343b3648b54dDifference Between Linux and Windows edit
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 edit
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)