Obtener el padre de un directorio en Bash

209

Si tengo una ruta de archivo como ...

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

¿Cómo cambio la cadena para que sea el directorio principal?

p.ej

/home/smith/Desktop
/home/smith/Desktop/
YTKColumba
fuente
44
Simplemente puede usar ' ..', pero tal vez eso no sea exactamente lo que tenía en mente.
Jonathan Leffler

Respuestas:

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

Funciona si también hay una barra inclinada final.

Michael Hoffman
fuente
14
las citas son importantes o
perderás
11
y la variante que estoy usando para obtener el padre del directorio de trabajo actual: parentdir=$(dirname `pwd`)
TheGrimmScientist
3
Tenga en cuenta que este método no es seguro para los nombres "feos". Un nombre como "-rm -rf" romperá el comportamiento deseado. Probablemente "." también lo hará. No sé si esa es la mejor manera, pero he creado una pregunta separada "centrada en la corrección" aquí: stackoverflow.com/questions/40700119/…
VasiliNovikov
44
@Christopher Shroba Sí, omití las citas y luego mi HDD terminó parcialmente borrado. No recuerdo exactamente lo que sucedió: presumiblemente un directorio de espacios en blanco sin comillas hizo que borrara el directorio principal en lugar del de destino; mi script simplemente borró la carpeta / home / xxx /.
catamphetamine
3
oh, está bien, así que no hay nada sobre el comando que pueda hacer que se pierdan datos a menos que ya esté emitiendo un comando de eliminación, ¿verdad? ¿Era solo una cuestión de la ruta que estabas tratando de eliminar dividiéndote en un personaje espacial y eliminando mucho más de lo esperado?
Christopher Shroba
18

... pero lo que se "ve aquí " está roto. Aquí está la solución:

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

Solo utilícelo echo $(cd ../ && pwd)mientras trabaja en el directorio cuyo directorio principal desea averiguar. Esta cadena también tiene el beneficio adicional de no tener barras diagonales finales.

Anuncio Endu
fuente
No necesitas echoaquí.
gniourf_gniourf
14

Claramente, el directorio principal se proporciona simplemente agregando el nombre de archivo punto-punto:

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

Pero debe querer la ruta resuelta (una ruta absoluta sin ningún componente de ruta punto-punto):

/home/smith/Desktop             # resolved path

El problema con las respuestas principales que usan dirnamees que no funcionan cuando ingresas una ruta con puntos-puntos:

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

Esto es más poderoso :

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

Puede alimentarlo /home/smith/Desktop/Test/.., pero también rutas más complejas como:

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

EDITAR: Si no funciona, verifique que cdno se haya modificado, como a veces es una práctica común,

$ 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
fuente
El uso evalNO es excelente si hay alguna posibilidad de analizar la entrada del usuario que podría ubicarlo en un lugar que no espera o ejecutar cosas malas contra su sistema. ¿Se puede usar en pushd $dir 2>&1 >/dev/null && pwd && popd 2>&1 >/dev/nulllugar de la eval?
dragon788
2
No necesitas evalnada: solo hazlo parentdir=$(cd -- "$dir" && pwd). Como cdse ejecuta en una subshell, no es necesario cdvolver a donde estábamos. El --está aquí en caso de que la expansión de $dircomience con un guión. Esto &&es solo para evitar parentdirtener un contenido no vacío cuando cdno tuvo éxito.
gniourf_gniourf
8

Motivación por otra respuesta

Me gusta el código muy corto, claro y garantizado. Punto de bonificación si no ejecuta un programa externo, ya que el día que necesite procesar una gran cantidad de entradas, será notablemente más rápido.

Principio

No estoy seguro de qué garantías tiene y quiere, por lo que ofrece de todos modos.

Si tiene garantías, puede hacerlo con un código muy corto. La idea es usar la función de sustitución de texto bash para cortar la última barra y lo que sigue.

Responda de casos simples a más complejos de la pregunta original.

Si se garantiza que la ruta finalizará sin barra inclinada (entrada y salida)

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

Si se garantiza que la ruta finalizará exactamente con una barra (entrada y salida)

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

Si la ruta de entrada puede terminar con cero o una barra inclinada (no más) y desea que la ruta de salida termine sin barra inclinada

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

Si la ruta de entrada puede tener muchas barras extrañas y desea que la ruta de salida finalice sin 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
fuente
1
Fantástica respuesta. Dado que parece que estaba buscando una forma de hacerlo desde un script (encuentre el directorio actual del script y su padre y haga algo en relación con el lugar donde vive el script), esta es una gran respuesta y puede tener aún más control sobre si la entrada tiene una barra inclinada final o no mediante el uso de expansión de parámetros contra la ${BASH_SOURCE[0]}cual sería la ruta completa al script si no se obtuvo a través de sourceo ..
dragon788
7

Si /home/smith/Desktop/Test/../es lo que quieres:

dirname 'path/to/child/dir'

como se ve aquí .

Jon Egeland
fuente
1
lo siento, quiero decir, como un script bash, tengo una variable con la ruta del archivo
YTKColumba
Ejecutar ese código me da el error bash: dirname 'Test': syntax error: invalid arithmetic operator (error token is "'Test'"). El código vinculado también está mal.
Michael Hoffman
intente simplemente ejecutar dirname Testdesde el directorio Testestá en.
Jon Egeland
1

Dependiendo de si necesita rutas absolutas, es posible que desee dar un paso adicional:

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

usa esto: export MYVAR="$(dirname "$(dirname "$(dirname "$(dirname $PWD)")")")"si quieres el 4to directorio padre

export MYVAR="$(dirname "$(dirname "$(dirname $PWD)")")" si quieres el tercer directorio padre

export MYVAR="$(dirname "$(dirname $PWD)")" si quieres el segundo directorio padre

Kaustubh
fuente
¡Gracias !, justo lo que estaba buscando.
Josue Abarca
0

feo pero 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
fuente
0

Comenzó a partir de la idea / comentario Charles Duffy - 17 de diciembre de 2014 a las 5:32 sobre el tema Obtener el nombre del directorio actual (sin ruta completa) en un 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
fuente
0

Si por cualquier razón usted está interesado en la navegación de un número específico de directorios también se puede hacer: nth_path=$(cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && cd ../../../ && pwd). Esto daría 3 directorios de padres arriba

ClimbingTheCurve
fuente
por qué $ {BASH_SOURCE [0]} en lugar de más simple $ BASH_SOURCE
Nam G VU