325

If I have a file path such as...

/home/smith/Desktop/Test
/home/smith/Desktop/Test/

How do I change the string so it will be the parent directory?

e.g.

/home/smith/Desktop
/home/smith/Desktop/
Nathan
  • 8,093
  • 8
  • 50
  • 76
YTKColumba
  • 4,023
  • 4
  • 19
  • 21

13 Answers13

510
dir=/home/smith/Desktop/Test
parentdir="$(dirname "$dir")"

Works if there is a trailing slash, too.

Michael Hoffman
  • 32,526
  • 7
  • 64
  • 86
  • 22
    the quotes inside are important or you're gonna loose all your data – catamphetamine Nov 07 '14 at 16:04
  • 20
    and the variant I'm using to get the parent of current working directory: `parentdir=$(dirname \`pwd\`)` – TheGrimmScientist Jul 11 '15 at 21:31
  • The parent of current working directory is simply `..`. – Michael Hoffman Jul 12 '15 at 10:35
  • If you want 2nd/3rd/4th parent directories, then see this answer. http://stackoverflow.com/questions/8426058/bash-get-the-parent-directory-of-current-directory/40632749#40632749 – Kaustubh Nov 16 '16 at 12:56
  • 4
    Note, this method is insecure for "ugly" names. A name like "-rm -rf" will break the desired behavior. Probably "." will, too. I don't know if that's the best way, but I've created a separate "correctness-focused" question here: http://stackoverflow.com/questions/40700119/get-parent-directory-for-a-file-in-bash – VasiliNovikov Nov 20 '16 at 04:05
  • 3
    @asdfasdfads Can you explain your comment? Why would data be lost if the quotes are omitted? – Christopher Shroba Nov 30 '16 at 13:11
  • 4
    @Christopher Shroba Yeah, I omitted the quotes and then my HDD ended up partially wiped. I don't remember exactly what has happened: presumably an unquoted whitespaced directory made it erase the parent dir instead of the target one - my script just erased the /home/xxx/ folder. – catamphetamine Dec 01 '16 at 10:45
  • 4
    oh okay, so there's nothing about the command that would cause any data to get lost unless you're already issuing a delete command, right? It was just a matter of the path you were trying to remove getting split on a space character and removing much more than expected? – Christopher Shroba Dec 01 '16 at 14:02
  • @TheGrimmScientist don't run your variant from "`/tmp/robert && sudo dd if=/dev/zero of=/dev/sda`". – mwfearnley Jan 14 '20 at 15:34
  • basename $(dirname $(pwd)) – Riveascore Dec 20 '22 at 02:14
40

Clearly the parent directory is given by simply appending the dot-dot filename:

/home/smith/Desktop/Test/..     # unresolved path

But you must want the resolved path (an absolute path without any dot-dot path components):

/home/smith/Desktop             # resolved path

The problem with the top answers that use dirname, is that they don't work when you enter a path with dot-dots:

$ dir=~/Library/../Desktop/../..
$ parentdir="$(dirname "$dir")"
$ echo $parentdir
/Users/username/Library/../Desktop/..   # not fully resolved

This is more powerful:

dir=/home/smith/Desktop/Test
parentdir=$(builtin cd $dir; pwd)

You can feed it /home/smith/Desktop/Test/.., but also more complex paths like:

$ dir=~/Library/../Desktop/../..
$ parentdir=$(builtin cd $dir; pwd)
$ echo $parentdir
/Users                                  # the fully resolved path!
 

NOTE: use of builtin ensures no user defined function variant of cd is called, but rather the default utility form which has no output.

Rian Rizvi
  • 9,989
  • 2
  • 37
  • 33
  • Using `eval` is NOT great if there is any chance of parsing user input that could put you somewhere you don't expect or run bad things against your system. Can you use `pushd $dir 2>&1 >/dev/null && pwd && popd 2>&1 >/dev/null` instead of the `eval`? – dragon788 Sep 24 '18 at 18:37
  • 3
    You don't need `eval` at all: just do `parentdir=$(cd -- "$dir" && pwd)`. Since the `cd` is run in a subshell, you don't need to `cd` back to where we were. The `--` is here in case the expansion of `$dir` starts with a hyphen. The `&&` is just to prevent `parentdir` to have a non-empty content when the `cd` didn't suceed. – gniourf_gniourf Nov 14 '19 at 11:51
  • @gniourf_gniourf thanks. updated the answer based on your advice – Rian Rizvi Sep 14 '20 at 18:00
  • I used this to get the parent directory of an application: `dir=$(builtin cd "../../.."; pwd)` when everything else failed, due to a space in the path name. The application was a pseudo-app built by Platypus, where the actual script was buried inside. Thanks – Manngo Sep 10 '21 at 08:09
37

Just use echo $(cd ../ && pwd) while working in the directory whose parent dir you want to find out. This chain also has the added benefit of not having trailing slashes.

Endu A-d
  • 379
  • 3
  • 2
21

...but what is "seen here" is broken. Here's the fix:

> pwd
/home/me
> x='Om Namah Shivaya'
> mkdir "$x" && cd "$x"
/home/me/Om Namah Shivaya
> parentdir="$(dirname "$(pwd)")"
> echo $parentdir
/home/me
Andreas Spindler
  • 7,568
  • 4
  • 43
  • 34
18

Motivation for another answer

I like very short, clear, guaranteed code. Bonus point if it does not run an external program, since the day you need to process a huge number of entries, it will be noticeably faster.

Principle

Not sure about what guarantees you have and want, so offering anyway.

If you have guarantees you can do it with very short code. The idea is to use bash text substitution feature to cut the last slash and whatever follows.

Answer from simple to more complex cases of the original question.

If path is guaranteed to end without any slash (in and out)

P=/home/smith/Desktop/Test ; echo "${P%/*}"
/home/smith/Desktop

If path is guaranteed to end with exactly one slash (in and out)

P=/home/smith/Desktop/Test/ ; echo "${P%/*/}/"
/home/smith/Desktop/

If input path may end with zero or one slash (not more) and you want output path to end without slash

for P in \
    /home/smith/Desktop/Test \
    /home/smith/Desktop/Test/
do
    P_ENDNOSLASH="${P%/}" ; echo "${P_ENDNOSLASH%/*}"
done

/home/smith/Desktop
/home/smith/Desktop

If input path may have many extraneous slashes and you want output path to end without slash

for P in \
    /home/smith/Desktop/Test \
    /home/smith/Desktop/Test/ \
    /home/smith///Desktop////Test// 
do
    P_NODUPSLASH="${P//\/*(\/)/\/}"
    P_ENDNOSLASH="${P_NODUPSLASH%%/}"
    echo "${P_ENDNOSLASH%/*}";   
done

/home/smith/Desktop
/home/smith/Desktop
/home/smith/Desktop
Stéphane Gourichon
  • 6,493
  • 4
  • 37
  • 48
  • 1
    Fantastic answer. Since it seems like he was looking for a way to do this from within a script (find the script's current directory and it's parent and do something relative to where the script lives) this is a great answer and you can have even more control over whether the input has a trailing slash or not by using parameter expansion against `${BASH_SOURCE[0]}` which would be the full path to the script if it wasn't sourced via `source` or `.`. – dragon788 Sep 24 '18 at 18:48
11

If you need just the name of parent directory:

parent_dir_name=$(basename $(dirname $PWD))
igolkotek
  • 1,687
  • 18
  • 16
9

If /home/smith/Desktop/Test/../ is what you want:

dirname 'path/to/child/dir'

as seen here.

Jon Egeland
  • 12,470
  • 8
  • 47
  • 62
4

Depending on whether you need absolute paths you may want to take an extra step:

child='/home/smith/Desktop/Test/'
parent=$(dirname "$child")
abs_parent=$(realpath "$parent")
gniourf_gniourf
  • 44,650
  • 9
  • 93
  • 104
Marcelo Lacerda
  • 847
  • 1
  • 15
  • 30
0

use this : export MYVAR="$(dirname "$(dirname "$(dirname "$(dirname $PWD)")")")" if you want 4th parent directory

export MYVAR="$(dirname "$(dirname "$(dirname $PWD)")")" if you want 3rd parent directory

export MYVAR="$(dirname "$(dirname $PWD)")" if you want 2nd parent directory

Kaustubh
  • 653
  • 6
  • 21
0

ugly but efficient

function Parentdir()

{

local lookFor_ parent_ switch_ i_

lookFor_="$1"

#if it is not a file, we need the grand parent
[ -f "$lookFor_" ] || switch_="/.."

#length of search string
i_="${#lookFor_}"

#remove string one by one until it make sens for the system
while [ "$i_" -ge 0 ] && [ ! -d "${lookFor_:0:$i_}" ];
do
    let i_--
done

#get real path
parent_="$(realpath "${lookFor_:0:$i_}$switch_")" 

#done
echo "
lookFor_: $1
{lookFor_:0:$i_}: ${lookFor_:0:$i_}
realpath {lookFor_:0:$i_}: $(realpath ${lookFor_:0:$i_})
parent_: $parent_ 
"

}

    lookFor_: /home/Om Namah Shivaya
{lookFor_:0:6}: /home/
realpath {lookFor_:0:6}: /home
parent_: /home 


lookFor_: /var/log
{lookFor_:0:8}: /var/log
realpath {lookFor_:0:8}: /UNIONFS/var/log
parent_: /UNIONFS/var 


lookFor_: /var/log/
{lookFor_:0:9}: /var/log/
realpath {lookFor_:0:9}: /UNIONFS/var/log
parent_: /UNIONFS/var 


lookFor_: /tmp//res.log/..
{lookFor_:0:6}: /tmp//
realpath {lookFor_:0:6}: /tmp
parent_: / 


lookFor_: /media/sdc8/../sdc8/Debian_Master//a
{lookFor_:0:35}: /media/sdc8/../sdc8/Debian_Master//
realpath {lookFor_:0:35}: /media/sdc8/Debian_Master
parent_: /media/sdc8 


lookFor_: /media/sdc8//Debian_Master/../Debian_Master/a
{lookFor_:0:44}: /media/sdc8//Debian_Master/../Debian_Master/
realpath {lookFor_:0:44}: /media/sdc8/Debian_Master
parent_: /media/sdc8 


lookFor_: /media/sdc8/Debian_Master/../Debian_Master/For_Debian
{lookFor_:0:53}: /media/sdc8/Debian_Master/../Debian_Master/For_Debian
realpath {lookFor_:0:53}: /media/sdc8/Debian_Master/For_Debian
parent_: /media/sdc8/Debian_Master 


lookFor_: /tmp/../res.log
{lookFor_:0:8}: /tmp/../
realpath {lookFor_:0:8}: /
parent_: /
0

Started from the idea/comment Charles Duffy - Dec 17 '14 at 5:32 on the topic Get current directory name (without full path) in a Bash script

#!/bin/bash
#INFO : https://stackoverflow.com/questions/1371261/get-current-directory-name-without-full-path-in-a-bash-script
# comment : by Charles Duffy - Dec 17 '14 at 5:32
# at the beginning :



declare -a dirName[]

function getDirNames(){
dirNr="$(  IFS=/ read -r -a dirs <<<"${dirTree}"; printf '%s\n' "$((${#dirs[@]} - 1))"  )"

for(( cnt=0 ; cnt < ${dirNr} ; cnt++))
  do
      dirName[$cnt]="$( IFS=/ read -r -a dirs <<<"$PWD"; printf '%s\n' "${dirs[${#dirs[@]} - $(( $cnt+1))]}"  )"
      #information – feedback
      echo "$cnt :  ${dirName[$cnt]}"
  done
}

dirTree=$PWD;
getDirNames;
kris
  • 392
  • 4
  • 16
0

if for whatever reason you are interested in navigating up a specific number of directories you could also do: nth_path=$(cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && cd ../../../ && pwd). This would give 3 parents directories up

ClimbingTheCurve
  • 323
  • 2
  • 14
-4

This would go up to the parent folder

cd ../
null canvas
  • 10,201
  • 2
  • 15
  • 18
  • This doesn't answer the question the OP was asking. This command will change the current directory during a terminal session. The OP was asking how to obtain string that contains the parent directory, without actually moving directories. – wisenickel Jul 25 '22 at 17:43