19
\$\begingroup\$

For this challenge, you will be given an absolute path, and a "new" path (which can be absolute or relative), and you need to return the final path.

For example, if your current directory was /var/tmp/test:

my_dir or my_dir/ should return /var/tmp/test/my_dir

../../my_dir should return /var/my_dir

/my_dir/./ should return /my_dir

../../../../../ should return /

To be more pedantic:

  • A directory is a non-empty string consisting of alphanumeric characters and the symbols -,_, or .
  • A path is a list of 0 or more directories, separated using /. An absolute path starts with a /, a relative path does not. Paths can include an ending /.

You need to "resolve" the second path, given the first path.

The process of resolving is:

  1. Test if the second path is relative. If so, then insert the absolute path's directories to the beginning of the second path.
  2. If any of the directories is .., then remove it and the preceding directory. If it is the first directory, then simply remove it.
  3. If any of the directories is ., then remove it.
  4. Output the final absolute path. You should not output an ending /.

You do not need to handle incorrect input. The commands should work, whether or not the directories passed actually exist on your machine. You can assume that everything is a directory, even if it has an extension.

Test cases

Absolute      New          Output
"/a/b/c"      "d"       -> "/a/b/c/d" 
"/a/b/c/"     "d"       -> "/a/b/c/d"
"/a/b/c/"     "d/"      -> "/a/b/c/d"
"/a/b/c"      "/d"      -> "/d"
"/a/b/c"      "/d/"     -> "/d"
"/../a/b/c/"  "d"       -> "/a/b/c/d"
"/a/../b/c/"  "d"       -> "/b/c/d"
"/a/b/../c"   "d"       -> "/a/c/d"
"/a/b/c/.."   "d"       -> "/a/b/d"
"/a/b/c/"     ".."      -> "/a/b"
"/a/b/c"      "../d"    -> "/a/b/d"
"/a/b/c"      "/../d"   -> "/d"
"/a/b/c"      ""        -> "/a/b/c"
"/a/b/c"      "."       -> "/a/b/c"
"/a/b/c"      "./d"     -> "/a/b/c/d"
"/a/b/c"      "/./d"    -> "/d"
"/a/b/c"      "d.txt"   -> "/a/b/c/d.txt"
"/a/b/c"      "d."      -> "/a/b/c/d."
"/a/b/c"      ".txt"    -> "/a/b/c/.txt"
"/a/b/c"      ".txt/d"  -> "/a/b/c/.txt/d"
"/a/b/."      "./././." -> "/a/b"
"/direc"      "tory"    -> "/direc/tory"
"/a-_.b/"     "__._-."  -> "/a-_.b/__._-."
"/a/b"        "../.."   -> "/"
"/a/b"        "../../.."-> "/"
"/a"          "../../.."-> "/"
"/"           ""        -> "/"
"/"           "a"       -> "/a"
"/.."         "a"       -> "/a"
"/."          ""        -> "/"

This is a , so make your submissions as short as possible in your favorite language!

\$\endgroup\$
6
  • \$\begingroup\$ Some answers appear to assume that files (or symlinks) with the same name as any part of the directory tree) do not exist on the machine. Is that allowed? \$\endgroup\$
    – Dennis
    Commented Jul 16, 2016 at 21:02
  • \$\begingroup\$ Can we take the two inputs in any order we wish? \$\endgroup\$
    – Downgoat
    Commented Jul 16, 2016 at 21:49
  • \$\begingroup\$ Stupid question... can I have side effects? Specifically, side effects like, um, mkdir $patha; cd $patha; mkdir $pathb; cd $pathb; echo `abspath` (or something)? \$\endgroup\$
    – cat
    Commented Jul 17, 2016 at 11:49
  • \$\begingroup\$ @dennis. The output of the programs should be independent of the file system \$\endgroup\$ Commented Jul 17, 2016 at 15:26
  • \$\begingroup\$ @downgoat that's fine \$\endgroup\$ Commented Jul 17, 2016 at 15:48

14 Answers 14

7
\$\begingroup\$

Retina, 44 bytes

+`.+ /| |/\.?/
/
+1`/?[^/]*/\.\.|/\.?$

^$
/

Input is expected to be the two paths separated by a single space.

Try it online! (The first line enables a linefeed-separated test suite.)

\$\endgroup\$
0
3
\$\begingroup\$

Python, 53 bytes

from os.path import*;p=lambda a,n:normpath(join(a,n))
\$\endgroup\$
0
3
\$\begingroup\$

Batch, 282 281 279 276 bytes

@echo off
set a=\
set r=%~2
if "%r%"=="" set r=%~1
if not %r:~,1%==/ set r=%~1/%~2
for %%a in (%r:/= %)do call:x %%a
if not %a%==\ set a=%a:~,-1%
echo %a:\=/%
exit/b
:x
if %1==. exit/b
if not %1==.. set a=%a%%1\&exit/b
if not %a%==\ for %%a in (%a:~,-1%)do set a=%%~pa

Annoyingly Batch expressions don't generally like empty variables. Edit: Saved 1 byte thanks to @CᴏɴᴏʀO'Bʀɪᴇɴ and 2 bytes thanks to @EʀɪᴋᴛʜᴇGᴏʟғᴇʀ (and a bunch of bytes on other answers too, although alas uncredited).

\$\endgroup\$
2
  • \$\begingroup\$ I think you can remove a space between call and :x`, no? \$\endgroup\$ Commented Jul 16, 2016 at 0:33
  • \$\begingroup\$ @CᴏɴᴏʀO'Bʀɪᴇɴ Huh, so you can. I've got a bunch of answers that need updating in that case... \$\endgroup\$
    – Neil
    Commented Jul 16, 2016 at 9:32
2
\$\begingroup\$

Python 2, 265 260 254 bytes

y=lambda:[x for x in raw_input().split("/")if x!=""and x!="."]
a=y();n=y();m=len(a)-1
while m>0:
 if a[m]==".."and m>0:del a[m];del a[m-1];m-=1
 elif a[m]=="..":del a[m]
 m-=1
for i in n:
 if i==".."and len(a)>0:del a[-1]
 else:a+=i,
print"/"+"/".join(a)
\$\endgroup\$
1
\$\begingroup\$

Python, 142 137 bytes

def p(a,n,r=[],S="/"):
 for s in[s for s in((n[:1]!=S)*a+S+n).split(S)if"."!=s and s]:e=s!="..";r=[s]*e+r[1-e:]
 return S+S.join(r[::-1])
\$\endgroup\$
1
\$\begingroup\$

Bash, 41 bytes

This bash script has the side effect of creating directories if they don't exist, but it should meet the requirements. Thanks Karl and Neil for your improvements.

mkdir -p $1;cd $1;mkdir -p $2;cd "$2";pwd

Usage: bash getpath.sh "absolute" "new"

If you don't like the stderr when second argument is an empty string, you can test for it as follows (48 bytes):

mkdir -p $1;cd $1;[ $2 ]&&mkdir -p $2&&cd $2;pwd

Previous 30 byte attempt (requires directories to exist): cd $1;[ $2 ]&&cd $2;echo pwd

\$\endgroup\$
14
  • \$\begingroup\$ The question says The commands should work, whether or not the directories passed actually exist on your machine. \$\endgroup\$
    – Dennis
    Commented Jul 16, 2016 at 4:20
  • \$\begingroup\$ Ah, I see. Too bad. \$\endgroup\$ Commented Jul 16, 2016 at 4:35
  • \$\begingroup\$ Hello, and welcome to PPCG! Normally, if your answer doesn't work, you delete it. You can click the delete link above this comment. \$\endgroup\$ Commented Jul 16, 2016 at 4:37
  • \$\begingroup\$ You could mkdir -p to make sure they exist. \$\endgroup\$
    – Karl Napf
    Commented Jul 16, 2016 at 4:42
  • \$\begingroup\$ Thanks, I'm trying a version with mkdir. I'll delete this answer and add a new one if I figure it out. \$\endgroup\$ Commented Jul 16, 2016 at 4:44
1
\$\begingroup\$

C#, 43 bytes

(x,y)=>Path.GetFullPath(Path.Combine(x,y));

Saved 1 byte thanks to @aloisdg

Path.Combine puts the arguments together, and Path.GetFullPath resolves the ..\s

\$\endgroup\$
3
  • \$\begingroup\$ Hello, and welcome to PPCG! This is not a valid program -- either include main and a class, or change it to a lanbda: a,b->... \$\endgroup\$ Commented Jul 16, 2016 at 4:36
  • \$\begingroup\$ I was going to post it :) Nice first submission! you can remove the space after the ,: (x, y) => (x,y) \$\endgroup\$
    – aloisdg
    Commented Jul 16, 2016 at 11:56
  • \$\begingroup\$ Also the C# Tips for golfing thread may interest you. \$\endgroup\$
    – aloisdg
    Commented Jul 16, 2016 at 11:57
1
\$\begingroup\$

Node REPL, 8 12 bytes

path.resolve

Luckily you don't have to require() standard modules in the REPL.

Test Suite

https://repl.it/Cclo/1

(If the output at the end is true, it matched)

\$\endgroup\$
0
1
\$\begingroup\$

Javascript, 210 bytes

function p(a,b){d='.';e=d+d;s='/';t='split';u='splice';r=(b[0]===s?[]:a[t](s)).concat(b[t](s));for(i=0;i<r.length;r[i]===e&&r[u](i?i-1:i,i?2:1)?(i&&i--):i++)(!r[i]||r[i]===d)&&r[u](i,1)&&i--;return s+r.join(s)}

Here is test suite

With linebreaks instead of semicolons:

function p(a,b) {
    d='.'
    e=d+d
    s='/'
    t='split'
    u='splice'

    r=(b[0]===s?[]:a[t](s)).concat(b[t](s))

    for(i=0;i<r.length;r[i]===e&&r[u](i?i-1:i,i?2:1)?(i&&i--):i++)
        (!r[i]||r[i]===d)&&r[u](i,1)&&i--

    return s+r.join(s)
}
\$\endgroup\$
0
\$\begingroup\$

Java 7, 83 bytes

String p(String a,String b){return Paths.get(a).resolve(b).normalize().toString();}

normalize is needed to deal with relative references. add is used to handle the second path starting with /, which Paths.get(a, b) won't handle as specified.

\$\endgroup\$
1
  • \$\begingroup\$ Hello, and welcome to PPCG! This is a good first post! \$\endgroup\$ Commented Jul 16, 2016 at 4:34
0
\$\begingroup\$

Bash, 38 bytes

[[ $2 = /* ]]||p=$1
realpath -sm $p/$2

Doesn't require root privileges and makes no assumptions about existing or non-existing files, directories or symbolic links.

Test it on Ideone.

How it works

[[ $2 = /* ]] tests if the second command-line argument begins with /.

If it doesn't, the path is relative and p=$1 sets variable p to the first command-line argument.

This way $p/$2 is /$2 if $2 is an absolute path and $1/$2 if it is a realtive one.

Finally, realpath -sm $p/$2 prints the canonical absolute path of $p/$2. The -s switch makes realpath ignore symbolic links, and the -m switch missing components.

\$\endgroup\$
0
\$\begingroup\$

Ruby, 16 bytes

Since apparently using a method from the standard library is allowed:

File.expand_path

See the test suite on repl.it.

\$\endgroup\$
4
  • \$\begingroup\$ Input via variables isn't allowed, but function submission, are, which means that you should shorten it to File.expand_path :) \$\endgroup\$ Commented Jul 18, 2016 at 17:42
  • \$\begingroup\$ I'd also recommend actually testing it against the test suite to make sure that it works correctly on all of the test cases. \$\endgroup\$ Commented Jul 18, 2016 at 17:43
  • \$\begingroup\$ @NathanMerrill I did, but I'll go ahead and stick something up on repl.it. \$\endgroup\$
    – Jordan
    Commented Jul 18, 2016 at 17:44
  • \$\begingroup\$ Edited to include test suite link. \$\endgroup\$
    – Jordan
    Commented Jul 18, 2016 at 18:11
0
\$\begingroup\$

GNU sed, 81 59 + 1 = 60 bytes

+1 byte for -r flag. Expects input on STDIN separates by a single space.

s:.+ /::
s:/? :/:
:
s:/$|[^/]+/+\.\.|\.(/|$):\1:
t
s:^/*:/:

Try it online!

Explanation

s:.+ /::  # If the second argument starts with a slash, drop the first argument
s:/? :/:  # Join the first and second arguments with a slash, dropping duplicate slashes
:
  s:/$|[^/]+/+\.\.|\.(/|$):\1:  # Drop trailing slashes, resolve double and single dots
  t                             # If the above substitution was made, branch to :
s:^/*:/:  # Ensure output begins with a single slash
\$\endgroup\$
0
\$\begingroup\$

Zsh, 15 bytes

a=$1/$2
<<<$a:a

The :a modifier does exactly this.

Try it online!

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.