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))
    (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)))
            (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.

Pages: 1 2

One Response to “Unix Paths”

  1. Josh said

    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)

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: