Obtendo o pai de um diretório no Bash

209

Se eu tiver um caminho de arquivo como ...

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

Como altero a string para que ela seja o diretório pai?

por exemplo

/home/smith/Desktop
/home/smith/Desktop/
YTKColumba
fonte
4
Você pode simplesmente usar ' ..', mas talvez isso não seja exatamente o que você tinha em mente.
Jonathan Leffler

Respostas:

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

Funciona se houver uma barra à direita também.

Michael Hoffman
fonte
14
as citações no interior são importantes ou você vai perder todos os seus dados
catamphetamine
11
e a variante que estou usando para obter o pai do diretório de trabalho atual: parentdir=$(dirname `pwd`)
TheGrimmScientist
3
Observe que esse método não é seguro para nomes "feios". Um nome como "-rm -rf" interromperá o comportamento desejado. Provavelmente "." vai também. Não sei se essa é a melhor maneira, mas criei uma pergunta "focada na correção" separada aqui: stackoverflow.com/questions/40700119/…
VasiliNovikov
4
@Christopher Shroba Sim, eu omiti as aspas e, em seguida, meu disco rígido acabou parcialmente limpo. Não me lembro exatamente do que aconteceu: presumivelmente, um diretório em branco com aspas em branco fez com que ele apagasse o diretório pai em vez do diretório de destino - meu script apenas apagou a pasta / home / xxx /.
catamphetamine
3
tudo bem, então não há nada no comando que possa causar a perda de dados, a menos que você já esteja emitindo um comando de exclusão, certo? Era apenas uma questão do caminho que você estava tentando remover, dividindo um caractere de espaço e removendo muito mais do que o esperado?
Christopher Shroba 01/12/16
18

... mas o que é "visto aqui " está quebrado. Aqui está a correção:

> 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
fonte
14

Basta usar echo $(cd ../ && pwd)enquanto trabalha no diretório cujo diretório pai você deseja descobrir. Essa cadeia também tem o benefício adicional de não ter barras à direita.

Anúncio final
fonte
Você não precisa echoaqui.
gniourf_gniourf
14

Claramente, o diretório pai é fornecido simplesmente acrescentando o nome do arquivo ponto a ponto:

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

Mas você deve querer o caminho resolvido (um caminho absoluto sem nenhum componente de caminho ponto a ponto):

/home/smith/Desktop             # resolved path

O problema com as principais respostas usadas dirnameé que elas não funcionam quando você insere um caminho com pontos-ponto:

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

Isso é mais poderoso :

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

Você pode alimentá-lo /home/smith/Desktop/Test/.., mas também caminhos mais complexos, como:

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

EDIT: se não funcionar, verifique cdse não foi modificado, como é prática comum às vezes,

$ 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
fonte
O uso evalNÃO é ótimo se houver alguma chance de analisar a entrada do usuário que possa colocá-lo em algum lugar que você não espera ou executar coisas ruins no sistema. Você pode usar em pushd $dir 2>&1 >/dev/null && pwd && popd 2>&1 >/dev/nullvez do eval?
precisa saber é o seguinte
2
Você não precisa de evalnada: apenas faça parentdir=$(cd -- "$dir" && pwd). Como o cdé executado em um subshell, você não precisa cdvoltar para onde estávamos. O --é aqui no caso a expansão do $dircomeça com um hífen. O &&é apenas para evitar parentdirter um conteúdo não vazio quando cdnão foi bem-sucedido.
gniourf_gniourf
8

Motivação para outra resposta

Eu gosto de um código muito curto, claro e garantido. Ponto de bônus se não executar um programa externo, já que no dia em que você precisa processar um grande número de entradas, será visivelmente mais rápido.

Princípio

Não tenho certeza sobre quais garantias você tem e deseja, então ofereça de qualquer maneira.

Se você tiver garantias, poderá fazê-lo com um código muito curto. A idéia é usar o recurso de substituição de texto bash para cortar a última barra e o que se segue.

Responda de casos simples a mais complexos da pergunta original.

Se for garantido que o caminho termina sem nenhuma barra (entrada e saída)

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

Se for garantido que o caminho termine com exatamente uma barra (entrada e saída)

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

Se o caminho de entrada puder terminar com zero ou uma barra (não mais) e você desejar que o caminho de saída termine sem barra

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

Se o caminho de entrada pode ter muitas barras estranhas e você deseja que o caminho de saída termine sem barra

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
fonte
1
Resposta fantástica. Como parece que ele estava procurando uma maneira de fazer isso de dentro de um script (encontre o diretório atual do script e seu pai e faça algo em relação ao local onde o script mora), essa é uma ótima resposta e você pode ter ainda mais controle sobre se a entrada tem uma barra final ou não, usando a expansão de parâmetro contra a ${BASH_SOURCE[0]}qual seria o caminho completo para o script se não fosse originado via sourceou ..
precisa saber é o seguinte
7

Se /home/smith/Desktop/Test/../é o que você deseja:

dirname 'path/to/child/dir'

como visto aqui .

Jon Egeland
fonte
1
desculpe, quero dizer, em como um script bash, tenho uma variável com o caminho do arquivo
YTKColumba
A execução desse código me dá o erro bash: dirname 'Test': syntax error: invalid arithmetic operator (error token is "'Test'"). O código vinculado também está errado.
Michael Hoffman
tente apenas correr a dirname Testpartir do diretório Testestá dentro.
Jon Egeland
1

Dependendo se você precisa de caminhos absolutos, convém dar um passo extra:

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

use isto: export MYVAR="$(dirname "$(dirname "$(dirname "$(dirname $PWD)")")")"se você quiser o 4º diretório pai

export MYVAR="$(dirname "$(dirname "$(dirname $PWD)")")" se você quiser o terceiro diretório pai

export MYVAR="$(dirname "$(dirname $PWD)")" se você quiser o segundo diretório pai

Kaustubh
fonte
Obrigado !, exatamente o que eu estava procurando.
27719 Josue Abarca
0

feio, mas eficiente

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
fonte
0

Iniciado a partir da idéia / comentário Charles Duffy - 17/12/14 às 5:32 sobre o tópico Obter o nome do diretório atual (sem o caminho completo) em um script 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
fonte
0

Se por qualquer motivo você está interessado em navegar até um determinado número de diretórios que você também pode fazer: nth_path=$(cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && cd ../../../ && pwd). Isso daria 3 diretórios para os pais

ClimbingTheCurve
fonte
por que $ {BASH_SOURCE [0]} em vez de mais simples $ BASH_SOURCE
Nam G VU