Reputation: 4323
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/
Upvotes: 356
Views: 387396
Reputation: 34324
dir=/home/smith/Desktop/Test
parentdir="$(dirname "$dir")"
Works if there is a trailing slash, too.
Upvotes: 553
Reputation: 771
As mentioned above, going from /some/folder/foo/bar/..
to /some/folder/foo
is rather trivial using buildin cd <...>; pwd
.
This however will have side effects when working with relative path as cd + pwd will resolve to an absolute path.
If you want to resolve folder/foo/bar/..
into folder/foo
, you can use sed
:
relative_path=folder/foo/bar/..
echo $relative_path | sed -E 's/(.*)\/(.*)\/\.\./\1/g'
The regexp used above is basically replacing any pat1/pat2/..
by pat1
.
That can also be used as a function:
function relative_parent() {
echo "$1" | sed -E 's/(.*)\/(.*)\/\.\./\1/g'
}
and called as:
relative_parent $relative_path
Upvotes: 0
Reputation: 1787
If you need just the name of parent directory:
parent_dir_name=$(basename $(dirname $PWD))
Upvotes: 14
Reputation: 10825
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.
Upvotes: 47
Reputation: 333
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
Upvotes: 0
Reputation: 314
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;
Upvotes: 0
Reputation: 6981
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.
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.
P=/home/smith/Desktop/Test ; echo "${P%/*}"
/home/smith/Desktop
P=/home/smith/Desktop/Test/ ; echo "${P%/*/}/"
/home/smith/Desktop/
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
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
Upvotes: 20
Reputation: 1
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_: /
Upvotes: 0
Reputation: 937
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")
Upvotes: 4
Reputation: 399
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.
Upvotes: 39
Reputation: 653
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
Upvotes: 0
Reputation: 8120
...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
Upvotes: 21
Reputation: 12613
If /home/smith/Desktop/Test/../
is what you want:
dirname 'path/to/child/dir'
as seen here.
Upvotes: 10