Directory calculation

19

2

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!

Nathan Merrill

Posted 2016-07-15T21:07:37.423

Reputation: 13 591

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? – Dennis – 2016-07-16T21:02:51.520

Can we take the two inputs in any order we wish? – Downgoat – 2016-07-16T21:49:04.040

Stupid question... can I have side effects? Specifically, side effects like, um, mkdir $patha; cd $patha; mkdir $pathb; cd $pathb; echo \abspath`` (or something)? – cat – 2016-07-17T11:49:26.183

@dennis. The output of the programs should be independent of the file system – Nathan Merrill – 2016-07-17T15:26:23.000

@downgoat that's fine – Nathan Merrill – 2016-07-17T15:48:51.173

@cat side effects are fine. – Nathan Merrill – 2016-07-17T15:49:20.350

Answers

7

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

Martin Ender

Posted 2016-07-15T21:07:37.423

Reputation: 184 808

3

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

Neil

Posted 2016-07-15T21:07:37.423

Reputation: 95 035

I think you can remove a space between call and :x`, no? – Conor O'Brien – 2016-07-16T00:33:36.180

@CᴏɴᴏʀO'Bʀɪᴇɴ Huh, so you can. I've got a bunch of answers that need updating in that case... – Neil – 2016-07-16T09:32:05.553

3

Python, 53 bytes

from os.path import*;p=lambda a,n:normpath(join(a,n))

orlp

Posted 2016-07-15T21:07:37.423

Reputation: 37 067

2

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)

acrolith

Posted 2016-07-15T21:07:37.423

Reputation: 3 728

1

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

jlynch630

Posted 2016-07-15T21:07:37.423

Reputation: 111

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->... – NoOneIsHere – 2016-07-16T04:36:11.473

I was going to post it :) Nice first submission! you can remove the space after the ,: (x, y) => (x,y) – aloisdg moving to codidact.com – 2016-07-16T11:56:21.940

Also the C# Tips for golfing thread may interest you.

– aloisdg moving to codidact.com – 2016-07-16T11:57:54.293

1

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

Bryn

Posted 2016-07-15T21:07:37.423

Reputation: 11

The question says The commands should work, whether or not the directories passed actually exist on your machine. – Dennis – 2016-07-16T04:20:36.957

Ah, I see. Too bad. – Bryn – 2016-07-16T04:35:46.713

Hello, and welcome to PPCG! Normally, if your answer doesn't work, you delete it. You can click the delete link above this comment. – NoOneIsHere – 2016-07-16T04:37:41.833

You could mkdir -p to make sure they exist. – Karl Napf – 2016-07-16T04:42:07.253

Thanks, I'm trying a version with mkdir. I'll delete this answer and add a new one if I figure it out. – Bryn – 2016-07-16T04:44:05.533

Thanks Karl, it works now if run with the necessary permissions. – Bryn – 2016-07-16T05:18:31.040

Why not use pwd instead of echo \pwd``? – Neil – 2016-07-16T09:33:26.367

As stderr is ignored by default, you can use mkdir -p $2;cd "$2". (cd $2 wouldn't work of course.) – Neil – 2016-07-16T09:35:41.427

Thanks Neil, those are some good improvements. I'll update my answer. – Bryn – 2016-07-16T11:39:38.280

@Bryn I don't think you're supposed to add a new answer as a replacement of this one. This one must be replaced if it doesn't work. (FYI I know it's edited now) – Erik the Outgolfer – 2016-07-16T22:07:08.623

@EʀɪᴋᴛʜᴇGᴏʟғᴇʀ Sorry, should I make a new answer now? I thought it would be useful to have the comment history so I edited my old answer. By the way, are there posting guidelines/FAQ somewhere that I am missing? – Bryn – 2016-07-17T01:42:28.793

@Bryn Editing and undeleting the existing answer is the preferred way. (I think that's what Erik meant, but I'm not sure.) Posts are never hard-deleted, so they clutter the thread forever. The help center has some guidelines.

– Dennis – 2016-07-17T01:50:24.273

@Dennis Thanks for the clarification. Undeleting makes sense. – Bryn – 2016-07-17T02:02:20.713

@Bryn That's what I said. Sorry for bad wording, I meant (I don't think you're supposed to add a new answer for the same language as this one, if this one is deleted. Instead, edit this one.) – Erik the Outgolfer – 2016-07-17T08:47:09.053

1

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])

orlp

Posted 2016-07-15T21:07:37.423

Reputation: 37 067

1

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)

Patrick Roberts

Posted 2016-07-15T21:07:37.423

Reputation: 2 475

1

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)
}

CShark

Posted 2016-07-15T21:07:37.423

Reputation: 191

0

Zsh, 15 bytes

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

The :a modifier does exactly this.

Try it online!

GammaFunction

Posted 2016-07-15T21:07:37.423

Reputation: 2 838

0

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.

jaxad0127

Posted 2016-07-15T21:07:37.423

Reputation: 281

Hello, and welcome to PPCG! This is a good first post! – NoOneIsHere – 2016-07-16T04:34:36.007

0

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.

Dennis

Posted 2016-07-15T21:07:37.423

Reputation: 196 637

0

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

Jordan

Posted 2016-07-15T21:07:37.423

Reputation: 5 001

0

Ruby, 16 bytes

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

File.expand_path

See the test suite on repl.it.

Jordan

Posted 2016-07-15T21:07:37.423

Reputation: 5 001

Input via variables isn't allowed, but function submission, are, which means that you should shorten it to File.expand_path :) – Nathan Merrill – 2016-07-18T17:42:46.250

I'd also recommend actually testing it against the test suite to make sure that it works correctly on all of the test cases. – Nathan Merrill – 2016-07-18T17:43:47.907

@NathanMerrill I did, but I'll go ahead and stick something up on repl.it. – Jordan – 2016-07-18T17:44:37.333

Edited to include test suite link. – Jordan – 2016-07-18T18:11:42.487