ChatGPT解决这个技术问题 Extra ChatGPT

Getting the parent of a directory in Bash

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/
You can simply use '..', but maybe that isn't quite what you had in mind.
'..' can only be used for directory path and not for path to a file.

M
Michael Hoffman
dir=/home/smith/Desktop/Test
parentdir="$(dirname "$dir")"

Works if there is a trailing slash, too.


the quotes inside are important or you're gonna loose all your data
and the variant I'm using to get the parent of current working directory: parentdir=$(dirname `pwd`)
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: stackoverflow.com/questions/40700119/…
@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.
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?
R
Riaz Rizvi

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.


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?
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 thanks. updated the answer based on your advice
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
E
Endu A-d

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.


You don't need echo here.
A
Andreas Spindler

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

S
Stéphane Gourichon

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

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 ..
J
Jon Egeland

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

dirname 'path/to/child/dir'

as seen here.


sorry, I mean in like a bash script, I have a variable with the file path
Running that code gives me the error bash: dirname 'Test': syntax error: invalid arithmetic operator (error token is "'Test'"). Linked code is also wrong.
try just running dirname Test from the directory Test is in.
i
igolkotek

If you need just the name of parent directory:

parent_dir_name=$(basename $(dirname $PWD))

g
gniourf_gniourf

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

realpath is not posix
Good to know. It's a software part of gnu core utils which may or may not be installed on your system.
K
Kaustubh

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


m
magoofromparis

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_: /

k
kris

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;

C
ClimbingTheCurve

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


why ${BASH_SOURCE[0]} instead of simpler $BASH_SOURCE
n
null canvas

This would go up to the parent folder

cd ../