Получение родительского каталога в Bash

Если у меня есть путь к файлу, например...

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

Как изменить строку, чтобы она была родительским каталогом?

например

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

10 ответов


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

работает, если есть косая черта.


...но что такое "видел"!--2-->здесь" разбито. Вот исправление:

> pwd
/home/me
> x='Om Namah Shivaya'
> mkdir "$x" && cd "$x"
/home/me/Om Namah Shivaya
> parentdir="$(dirname "$(pwd)")"
> echo $parentdir
/home/me

ясно, что родительский каталог задается простым добавлением имени файла точки:

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

но вы должны хотеть разрешить путь (абсолютный путь без каких-либо компонентов пути точки-точки):

/home/smith/Desktop             # resolved path

проблема с верхними ответами, которые используют dirname, это то, что они не работают, когда вы вводите путь с точками:

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

это более мощный:

dir=/home/smith/Desktop/Test
parentdir=`eval "cd $dir;pwd;cd - > /dev/null"`

вы можете кормить его /home/smith/Desktop/Test/.., но также более сложные пути, такие как:

$ dir=~/Library/../Desktop/../..
$ parentdir=`eval "cd $dir;pwd;cd - > /dev/null"`
$ echo $parentdir
/Users                                  # the fully resolved path!

Если /home/smith/Desktop/Test/../ что требуется:

dirname 'path/to/child/dir'

Как видно здесь.


просто использовать echo $(cd ../ && pwd) во время работы в каталоге, родительский каталог которого вы хотите узнать. Эта цепочка также имеет дополнительное преимущество, не имея хвостовых косых черт.


в зависимости от того, нужны ли вам абсолютные пути, вы можете сделать дополнительный шаг:

child='/home/smith/Desktop/Test/'
parent=$(dirname "$child")
abs_parent=$(realpath "$parent")

мотивация для другого ответа

мне нравится очень короткий, ясный, гарантированный код. Бонусный балл если не запустить внешнюю программу, так как за день нужно обработать огромное количество записей, то это будет заметно быстрее.

принцип

не уверен, какие гарантии у вас есть и хотите, поэтому предлагаю в любом случае.

если у вас есть гарантии, вы можете сделать это с очень короткий код. Идея состоит в том, чтобы использовать функцию подстановки текста bash, чтобы вырезать последний Слэш и все, что последует.

ответ от простых до более сложных случаев исходного вопроса.

если путь гарантированно заканчивается без косой черты (in и out)

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

если путь гарантированно заканчивается ровно одной косой чертой (in и out)

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

используйте этот : export MYVAR="$(dirname "$(dirname "$(dirname "$(dirname $PWD)")")")" Если вы хотите 4-й родительский каталог

export MYVAR="$(dirname "$(dirname "$(dirname $PWD)")")" если вы хотите 3-й родительский каталог

export MYVAR="$(dirname "$(dirname $PWD)")" если вы хотите 2-й родительский каталог


некрасиво, но эффективным!--4-->

function Parentdir()

{

local lookFor_ parent_ switch_ i_

lookFor_=""

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

началось с идеи / комментария Чарльза Даффи-Dec 17 ' 14 в 5: 32 по теме получить текущее имя каталога (без полного пути) в сценарии Bash

#!/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;