23

I'm using PWD to get the present working directory. Is there a SED or regex that I can use to, say, get the full path two parents up?

Steve
  • 341
  • 1
  • 4
  • 9

5 Answers5

22

This should work in POSIX shells:

echo ${PWD%/*/*}

which will give you an absolute path rather than a relative one.

Also, see my answer here where I give two functions:

cdn () { pushd .; for ((i=1; i<=$1; i++)); do cd ..; done; pwd; }

which goes up n levels given n as an argument.

And:

cdu () { cd "${PWD%/$1/*}/$1"; }

which goes up to a named subdirectory above the current working directory.

Dennis Williamson
  • 346,391
  • 90
  • 374
  • 439
21

Why sed or regex? Why not dirname:

parent=`dirname $PWD`
grandparent=`dirname $parent`

Edit:

@Daentyh points out in the comments that apparently $() is preferred over backquotes `` for command substitution in POSIX-compliant shells. I don't have experience with them. More info:

http://www.opengroup.org/onlinepubs/009695399/utilities/xcu_chap02.html#tag_02_06_03

So, if this applies to your shell, you can (should?) use:

parent=$(dirname $PWD)
grandparent=$(dirname $parent)
Bert F
  • 85,407
  • 12
  • 106
  • 123
  • 4
    You should use `$()` over `\`\`` since he specified bash. – Daenyth Sep 24 '10 at 19:17
  • @Daenyth - true, I was actually thinking of suggesting he retag the question (or allow me to retag it) to `sh` or `shell-scripting` since the question wasn't really `bash` specific. @Steve - what do you think? – Bert F Sep 24 '10 at 19:33
  • 2
    Even so, `$()` is still POSIX sh. (Not all sh are POSIX though) – Daenyth Sep 24 '10 at 19:37
  • 2
    Further nag: You should quote PWD as `"$PWD"`, otherwise spaces in a directory name will break you. @Benoit's solution is far simpler though and will work anywhere without trouble. `dirname` is not entirely portable also. – Daenyth Sep 24 '10 at 19:52
  • 1
    Beware that grandparent of `./foo` will be `.`. – Christoffer Hammarström Oct 01 '14 at 08:34
  • Loved this solution, I used this like `SERVICE_PATH=$(dirname $(dirname $(realpath $0)))` in a script that was inside a scripts folder and needed that to run in the parent and it just made my life easier. Thank you very much. – Gilberto Treviño May 18 '21 at 22:07
  • I dislike this, because going four levels up (which I need) spawns four recursive subshells, and is verbose. See the answer of Dennis Williamson, which is much better in my opinion. – Martin Grönlund Sep 16 '22 at 07:36
6

why not use

"${PWD}/../.."

?

Benoit
  • 76,634
  • 23
  • 210
  • 236
  • 1
    I prefer this simple approach. `realpath "${PWD}/../.."` and it should be much safer than regex especially when dealing with non-ascii languages. – taiyodayo May 17 '22 at 03:24
2

Not sed or regex, but this does do arbitrary parent quantity:

$ cd $(mktemp -dp $(mktemp -dp $(mktemp -dp $(mktemp -d)))) && \
> n=3 && \
> readlink -f  ${PWD}/$(for i in $(seq ${n}); do echo -n '../' ; done)
/tmp/tmp.epGcUeLV9q

In this example, I cd into a 5-deep temporary directory, assign n=3, construct a relative path n levels up from ${PWD}, and -f, --canonicalize the result with readlink.

rubicks
  • 4,912
  • 1
  • 30
  • 41
1

Here's the regex that works for me. It's a little different between grep and Perl/sed:

The extended regex breaks up paths into 0 or more groups of /ABC123 anchored to the end of line, essentially working backward. (.*) consumes everything prior to this match and replaces it.

user@home:~/adir/bdir$ pwd
/home/user/adir/bdir

user@home:~/adir/bdir$ pwd | perl -pe 's|(.*)((/.*?){0})$|\1|'
/home/user/adir/bdir

user@home:~/adir/bdir$ pwd | perl -pe 's|(.*)((/.*?){1})$|\1|'
/home/user/adir

user@home:~/adir/bdir$ pwd | sed -r 's|(.*)((/.*?){2})$|\1|'
/home/user

user@home:~/adir/bdir$ pwd | sed -r 's|(.*)((/.*?){3})$|\1|'
/home

user@home:~/adir/bdir$ pwd | sed -r 's|(.*)((/.*?){4})$|\1|'

Grep can simulate substitution using a positive look ahead (?= which tells grep to match everything except the pattern. -Po tells grep to use Perl regex and show only the match.

user@home:~/adir/bdir$ pwd
/home/user/adir/bdir

user@home:~/adir/bdir$ pwd | grep -Po '(.*)(?=((/.*?){0})$)'
/home/user/adir/bdir

user@home:~/adir/bdir$ pwd | grep -Po '(.*)(?=((/.*?){1})$)'
/home/user/adir

user@home:~/adir/bdir$ pwd | grep -Po '(.*)(?=((/.*?){2})$)'
/home/user

user@home:~/adir/bdir$ pwd | grep -Po '(.*)(?=((/.*?){3})$)'
/home

user@home:~/adir/bdir$ pwd | grep -Po '(.*)(?=((/.*?){4})$)'

Of course it works equally well for Windows style paths:

C:\home\user\adir\bdir> cd
C:\home\user\adir\bdir

C:\home\user\adir\bdir> cd | perl -pe 's|(.*)((\\.*?){0})$|\1|'
C:\home\user\adir\bdir

C:\home\user\adir\bdir> cd | sed -r 's|(.*)((\\.*?){1})$|\1|'
C:\home\user\adir

C:\home\user\adir\bdir> cd | grep -Po '(.*)(?=((\\.*?){2})$)'
C:\home\user

C:\home\user\adir\bdir> cd | grep -Po '(.*)(?=((\\.*?){3})$)'
C:\home

C:\home\user\adir\bdir> cd | grep -Po '(.*)(?=((\\.*?){4})$)'
C:

Sorry for the edits but I've been working on this enigma for about 16 hours now. Just kept trying different permutations and re-reading the regex docs. It had to sink in eventually.

noabody
  • 179
  • 4