Unix Paths
August 13, 2013
There are two tasks to be done. First, we have to check if our target path is relative, and — if so — make it an absolute path. Second, we need to minimize the absolute path by removing any “..” elements and the directory names that precede them.
The first task is straight-forward enough. If the path begins with a slash, we don’t do anything. Otherwise, we prepend the current directory and a slash onto it.
(define (absolutize-path current-directory path)
(if (char=? #\/ (string-ref path 0))
path
(string-append current-directory "/" path)))
For the second task, we divide the string into a list of elements, then move each element from an input list to an output list. When we see a “..” element, however, we toss it and also remove the last element we added to the output list. Finally, we reverse the output list to get it back into the correct order and turn the list back into a slash-delimited string.
(define (minimize-path path)
(let loop ((in (string-split #\/ path))
(out '()))
(cond ((null? in)
(string-join #\/ (reverse out)))
((string=? ".." (car in))
(loop (cdr in)
(cdr out)))
(else
(let ((x (car in)))
(loop (cdr in)
(cons x out)))))))
Our final function merely calls the other two.
(define (resolve-path current-directory path)
(minimize-path (absolutize-path current-directory path)))
Then some basic testing.
(assert (resolve-path "/a/b/c" "/d/e/f") "/d/e/f")
(assert (resolve-path "/a/b/c" "../d/e") "/a/b/d/e")
(assert (resolve-path "/a/b/c" "../../d/e") "/a/d/e")
We used string-split, string-join and the assert macro from the Standard Prelude. You can see and run the entire program at http://programmingpraxis.codepad.org/9DxHDkA0.
This works for paths starting with ‘.’, as well for the convoluted (‘/home/josh’, ‘./../josh/file.txt’) -> ‘/home/josh/file.txt’
def abs_path(cwd, fp): if fp.startswith('/'): return fp if fp.startswith('./'): return abs_path(cwd, fp[2:]) if fp.startswith('../'): return abs_path(cwd.rsplit('/', 1)[0], fp[3:]) return '%s/%s' % (cwd, fp)