[EMJ] Dec 14th 2004 - I know this isn't really difficult, but if someone's got it somewhere... If I have two file paths a and b, how can I derive a relative path to refer to a from the location of b (the files and paths may not exist)? [CMcC] there's a [tcllib] [fileutil] ''relative'' and a ''prefix'' command which facilitates this kind of file name manipulation. [MG] Dec 14th 2004 - This piqued my curiousity, so I had a quick go at getting something that works. It's only very lightly tested, but the tests I did worked OK. This won't work if you try giving it two files on different drives/volumes, though, and will probably loop indefinately; I wouldn't recommend trying it :D Someone else may well come up with something neater than this, but in the mean time... set path1 {C:/Documents and Settings/Griffiths/leaflet.pub} set path2 {C:/CONFIG.SYS} proc relTo {a b} { set a [file dirname [file normalize $a]] set b [file dirname [file normalize $b]] set aa [file split $a] set bb [file split $b] if { [llength $aa] < [llength $bb] } { set tmp $aa set aa $bb set bb $tmp set switch 1 unset tmp } else { set switch 0 } if { [llength $aa] == [llength $bb] } { if { $aa == $bb } { return "."; } set i 0 while { $i < [llength $aa] } { if { [join [lrange $aa 0 end-$i]] == [join [lrange $bb end-$i]] } { break; } incr i } return [string repeat ".." $i]; } set i 0 while { [lindex $aa $i] == [lindex $bb $i] } { incr i } set i [expr { [llength $aa] + 1 - $i }] set sep [file separator] if { $switch } { set string . for {set x 1} {$x <= $i} {incr x} { set string "$string$sep[lindex $aa $x]" incr i -1 } return $string; } else { return "[string repeat "..$sep" [expr {$i-1}]].."; } };# relTo relTo $path1 $path2 % ..\..\.. relTo $path2 $path1 % .\Documents and Settings\Griffiths You can then, for instance, cd [file dirname $path1] cd [relTo $path1 $path2] to get from the directory of one file to another. And then to get back, cd [relTo $path2 $path1] [EMJ] Dec 16th 2004 - Thanx very much. Since it won't do up the file tree and down a different branch, and also was giving funny answers on Linux, I started to play around with it, eventually ending up with the following, which does what I want: # get relative path to target file from current file # arguments are file names, not directory names (not checked) proc pathTo {target current} { set cc [file split [file normalize $current]] set tt [file split [file normalize $target]] if {![string equal [lindex $cc 0] [lindex $tt 0]]} { # not on *n*x then return -code error "$target not on same volume as $current" } while {[string equal [lindex $cc 0] [lindex $tt 0]] && [llength $cc] > 1} { # discard matching components from the front (but don't # do the last component in case the two files are the same) set cc [lreplace $cc 0 0] set tt [lreplace $tt 0 0] } set prefix "" if {[llength $cc] == 1} { # just the file name, so target is lower down (or in same place) set prefix "." } # step up the tree (start from 1 to avoid counting file itself for {set i 1} {$i < [llength $cc]} {incr i} { append prefix " .." } # stick it all together (the eval is to flatten the target list) return [eval file join $prefix $tt] } ---- '''[AlexD] - 2012-06-03 11:12:23''' In my case it was better to get relative path to target file from current path (not a file name). So I did some changes to the code provided by EMJ: ====== # Get relative path to target file from current path # First argument is a file name, second a directory name (not checked) proc relTo {targetfile currentpath} { set cc [file split [file normalize $currentpath]] set tt [file split [file normalize $targetfile]] if {![string equal [lindex $cc 0] [lindex $tt 0]]} { # not on *n*x then return -code error "$targetfile not on same volume as $currentpath" } while {[string equal [lindex $cc 0] [lindex $tt 0]] && [llength $cc] > 0} { # discard matching components from the front set cc [lreplace $cc 0 0] set tt [lreplace $tt 0 0] } set prefix "" if {[llength $cc] == 0} { # just the file name, so targetfile is lower down (or in same place) set prefix "." } # step up the tree for {set i 0} {$i < [llength $cc]} {incr i} { append prefix " .." } # stick it all together (the eval is to flatten the targetfile list) return [eval file join $prefix $tt] } ====== <> File