file join

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 join name ?name ...?


official reference


file join joins one or more strings into a file path using the correct platform-dependent separators. It is the only portable way to compose file names. 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



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 Windows, a UNC network path is treated similar to a drive specification:

% file join //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

On Unix, a leading // is removed:

% file join //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.


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:

% file join {\foo\bar\grill}

Back the other way with file nativename:

% file nativename /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 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 fileutil::lexnormalize