Uzyskiwanie elementu nadrzędnego katalogu w Bash

207

Jeśli mam ścieżkę do pliku, taką jak ...

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

Jak zmienić ciąg, aby był to katalog nadrzędny?

na przykład

/home/smith/Desktop
/home/smith/Desktop/
YTKColumba
źródło
4
Możesz po prostu użyć „ ..”, ale być może nie o to ci chodziło.
Jonathan Leffler,

Odpowiedzi:

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

Działa, jeśli występuje również ukośnik końcowy.

Michael Hoffman
źródło
14
cytaty w środku są ważne, inaczej stracisz wszystkie swoje dane
catamphetamine
10
i wariant, którego używam, aby uzyskać element nadrzędny bieżącego katalogu roboczego: parentdir=$(dirname `pwd`)
TheGrimmScientist
3
Uwaga: ta metoda nie jest bezpieczna w przypadku „brzydkich” nazw. Nazwa taka jak „-rm -rf” złamie pożądane zachowanie. Prawdopodobnie "." też. Nie wiem, czy to najlepszy sposób, ale stworzyłem tutaj osobne pytanie „skoncentrowane na poprawności”: stackoverflow.com/questions/40700119/…
VasiliNovikov
4
@Christopher Shroba Tak, pominąłem cytaty, a potem mój dysk twardy został częściowo wyczyszczony. Nie pamiętam dokładnie, co się wydarzyło: prawdopodobnie niecytowany katalog z białymi odstępami spowodował usunięcie katalogu nadrzędnego zamiast katalogu docelowego - mój skrypt właśnie skasował folder / home / xxx /.
katamfetamina
3
o dobrze, więc nie ma nic w poleceniu, które spowodowałoby utratę danych, chyba że wydałeś już polecenie usuwania, prawda? To była tylko kwestia ścieżki, którą próbujesz usunąć, dzieląc się na znak spacji i usuwając znacznie więcej, niż się spodziewałem?
Christopher Shroba
18

... ale to, co „widzi się tutaj ”, jest zepsute. Oto poprawka:

> 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
źródło
14

Po prostu użyj echo $(cd ../ && pwd)podczas pracy w katalogu, którego katalog macierzysty chcesz znaleźć. Dodatkową zaletą tego łańcucha jest brak końcowych ukośników.

Endu Ad
źródło
Nie potrzebujesz echotutaj.
gniourf_gniourf
14

Oczywiście katalog nadrzędny jest podawany przez dodanie nazwy pliku kropka-kropka:

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

Ale musisz mieć rozstrzygniętą ścieżkę (ścieżka bezwzględna bez żadnych składników ścieżki kropka-kropka):

/home/smith/Desktop             # resolved path

Problem z najczęściej używanymi odpowiedziami dirnamepolega na tym, że nie działają one po wprowadzeniu ścieżki z kropkami:

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

Jest to bardziej wydajne :

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

Możesz go karmić /home/smith/Desktop/Test/.., ale także bardziej złożone ścieżki, takie jak:

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

EDYCJA: Jeśli to nie działa, sprawdź, czy cdnie został zmodyfikowany, jak to często bywa,

$ which cd
cd is a function  #cd was modified to print extra info, so use "builtin cd" to override
cd ()
{
    builtin cd "$@";
    ls -FGAhp
}
...
Riaz Rizvi
źródło
Używanie evalNIE jest świetne, jeśli istnieje szansa na przeanalizowanie danych wejściowych użytkownika, które mogą umieścić cię w miejscu, którego się nie spodziewasz, lub uruchomić złe rzeczy w systemie. Czy możesz użyć pushd $dir 2>&1 >/dev/null && pwd && popd 2>&1 >/dev/nullzamiast eval?
dragon788,
2
W ogóle nie potrzebujesz eval: po prostu rób parentdir=$(cd -- "$dir" && pwd). Ponieważ cdjest uruchamiany w podpowłoce, nie musisz cdwracać do miejsca, w którym byliśmy. --Jest tutaj w przypadku ekspansja $dirrozpoczyna się od myślnika. Chodzi &&tylko o to, aby nie parentdirmieć niepustej zawartości, gdy cdnie została zasysana.
gniourf_gniourf
8

Motywacja do kolejnej odpowiedzi

Lubię bardzo krótki, przejrzysty, gwarantowany kod. Punkt bonusowy, jeśli nie uruchomi zewnętrznego programu, ponieważ dzień, w którym musisz przetworzyć ogromną liczbę wpisów, będzie zauważalnie szybszy.

Zasada

Nie jestem pewien, jakie gwarancje masz i chcesz, więc i tak oferuj.

Jeśli masz gwarancje, możesz to zrobić za pomocą bardzo krótkiego kodu. Chodzi o to, aby użyć funkcji zastępowania tekstu bash, aby wyciąć ostatni ukośnik i wszystko, co następuje.

Odpowiedz od prostych do bardziej złożonych przypadków pierwotnego pytania.

Jeśli ścieżka kończy się bez żadnego ukośnika (wejście i wyjście)

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

Jeśli ścieżka kończy się dokładnie jednym ukośnikiem (wejście i wyjście)

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

Jeśli ścieżka wejściowa może kończyć się zero lub jednym ukośnikiem (nie więcej) i chcesz, aby ścieżka wyjściowa kończyła się bez ukośnika

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

Jeśli ścieżka wejściowa może zawierać wiele obcych ukośników i chcesz, aby ścieżka wyjściowa kończyła się bez ukośnika

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
źródło
1
Fantastyczna odpowiedź. Ponieważ wygląda na to, że szukał sposobu, aby to zrobić ze skryptu (znajdź bieżący katalog skryptu i jego obiekt nadrzędny i zrób coś w odniesieniu do miejsca, w którym skrypt się znajduje), jest to świetna odpowiedź i możesz mieć jeszcze większą kontrolę nad tym, czy wejście ma ukośnik końcowy lub nie poprzez użycie rozszerzenia parametru, w stosunku do ${BASH_SOURCE[0]}którego byłaby pełna ścieżka do skryptu, gdyby nie została uzyskana za pośrednictwem sourcelub ..
dragon788
7

Jeśli /home/smith/Desktop/Test/../tego chcesz:

dirname 'path/to/child/dir'

jak widać tutaj .

Jon Egeland
źródło
1
przepraszam, mam na myśli jak skrypt bash, mam zmienną ze ścieżką do pliku
YTKColumba
Uruchomienie tego kodu powoduje błąd bash: dirname 'Test': syntax error: invalid arithmetic operator (error token is "'Test'"). Połączony kod jest również niepoprawny.
Michael Hoffman,
spróbuj po prostu uruchomić dirname Testz katalogu Testjest w.
Jon Egeland
1

W zależności od tego, czy potrzebujesz ścieżek bezwzględnych, możesz zrobić dodatkowy krok:

child='/home/smith/Desktop/Test/'
parent=$(dirname "$child")
abs_parent=$(realpath "$parent")
Marcelo Lacerda
źródło
0

Użyj tego : export MYVAR="$(dirname "$(dirname "$(dirname "$(dirname $PWD)")")")" jeśli chcesz 4. katalog nadrzędny

export MYVAR="$(dirname "$(dirname "$(dirname $PWD)")")" jeśli chcesz trzeci katalog nadrzędny

export MYVAR="$(dirname "$(dirname $PWD)")" jeśli chcesz drugi katalog nadrzędny

Kaustubh
źródło
Dzięki !, właśnie tego szukałem.
Josue Abarca
0

brzydki, ale wydajny

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_: /
magoofromparis
źródło
0

Zaczął od pomysłu / komentarza Charles Duffy - 17 grudnia 14 'o 5:32 na temat Uzyskaj nazwę bieżącego katalogu (bez pełnej ścieżki) w skrypcie Bash

#!/bin/bash
#INFO : /programming/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
źródło
0

Jeśli z jakiegoś powodu jesteś zainteresowany poruszania się określony numer katalogów można także zrobić: nth_path=$(cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && cd ../../../ && pwd). Dałoby to 3 katalogi rodziców

ClimbingTheCurve
źródło
dlaczego $ {BASH_SOURCE [0]} zamiast prostszego $ BASH_SOURCE
Nam G VU