¿Cómo puedo encontrar el primer directorio que falta en una ruta larga?

14

Imagina que tengo un camino que no existe:

$ ls /foo/bar/baz/hello/world
ls: cannot access /foo/bar/baz/hello/world: No such file or directory

Pero digamos /foo/bar que existe. ¿Hay alguna forma rápida de determinar que ese bazes el punto de ruptura en el camino?

Estoy usando Bash

kuzzooroo
fuente
access(2)no es muy granular, así que la solución general implica escribir algo para repetir y probar cada elemento de la ruta a su vez ...
thrig

Respuestas:

5

Dado un nombre de ruta canónico, como el suyo, esto funcionará:

set -f --; IFS=/
for p in $pathname
do    [ -e "$*/$p" ] || break
      set -- "$@" "$p"
done; printf %s\\n "$*"

Eso imprime a través del último componente completamente existente / accesible de $pathname, y coloca cada uno de ellos por separado en la matriz arg. El primer componente inexistente no se imprime, pero se guarda en $p.

Puede abordarlo de manera opuesta:

until cd -- "$path" && cd -
do    case   $path  in
      (*[!/]/*)
              path="${path%/*}"
;;    (*)   ! break
      esac
done  2>/dev/null   && cd -

Eso volverá adecuadamente o disminuirá $pathsegún sea necesario. Se niega a intentar un cambio /, pero si tiene éxito, imprimirá tanto su directorio de trabajo actual como el directorio al que cambia a stdout. Su corriente $PWDtambién se incluirá $OLDPWD.

mikeserv
fuente
Claramente: for p in $pathnameestará sujeto a "división de palabras" y "expansión de nombre de ruta".
1
@BinaryZebra: sí, está dividido $IFS. así es exactamente como funciona. no está sujeto a la expansión del nombre de ruta, excepto que la variable aquí llamada $pathnamese expande a una matriz de componentes de ruta dividida en $IFS.
mikeserv
Usted está evitando la "expansión del nombre de ruta" en la var $pathname mediante el uso de set -f"que no se devuelve a su valor por defecto.
@BinaryZebra: no estoy evitando nada. Estoy especificando el entorno que necesito para hacer esto de manera robusta. eso es todo. no, no se devuelve a su valor predeterminado y tampoco se instalará en la computadora del autor de la pregunta ni me hará el desayuno.
mikeserv
1
@BinaryZebra: por cierto, si, como debería hacer cualquier buen programador, a menudo te preocupa preocuparte por afectar / recuperar innecesariamente tu entorno de ejecución, es posible que te interese ns, lo que, si se usa aquí, haría que esta discusión fuera discutible.
mikeserv
12

Una de mis utilidades favoritas es namei, parte util-linuxy, por lo tanto, generalmente presente solo en Linux:

$ namei /usr/share/foo/bar
f: /usr/share/foo/bar
 d /
 d usr
 d share
   foo - No such file or directory

Pero su salida no es muy analizable. Por lo tanto, si solo desea señalar que falta algo, nameipuede ser útil.

Es útil para solucionar problemas generales al acceder a una ruta, ya que puede hacer que indique si un componente es un enlace o un punto de montaje, así como sus permisos:

$ ln -sf /usr/foo/bar /tmp/
$ namei -lx /tmp/bar
f: /tmp/bar
Drwxr-xr-x root    root    /
Drwxrwxrwt root    root    tmp
lrwxrwxrwx muru    muru    bar -> /usr/foo/bar
Drwxr-xr-x root    root      /
drwxr-xr-x root    root      usr
                             foo - No such file or directory

La capital Dindica un punto de montaje.

muru
fuente
@ StéphaneChazelas Esperaba que los equivalentes pudieran estar presentes en otros sistemas. ¿Nada para los BSD?
Muru
nameifunciona para mí siempre que lo alimente, un camino que existe, pero cuando le doy uno que no, obtengo namei: failed to stat: /usr/share/foo/bar: No such file or directory.
kuzzooroo
@kuzzooroo, ¿qué versión de namei?
muru
mi versión de namei no proporciona una versión en su ayuda ni en su página de manual y no admite una bandera que proporcione información de la versión. Puedo determinar que proviene del paquete linux-utils 2.16-1ubuntu5 pero no puedo entender qué implica eso para la versión de namei.
kuzzooroo
@kuzzooroo Dado que incluso Ubuntu 12.04 tiene 2.20.1 , debes tener una versión muy antigua de Ubuntu. 10.04?
muru
1

Algo así (teniendo en cuenta los nombres de ruta con espacios en blanco incrustados):

#!/bin/sh
explain() {
    if [ -d "$1" ]
    then
        printf "\t%s: is a directory\n" "$1"
    elif [ -e "$1" ]
    then
        printf "\t%s: is not a directory\n" "$1"
    else
        printf "\t%s: does not exist\n" "$1"
    fi
}

for item in "$@"
do
    last=
    test="$item"
    printf "testing: '%s'\n" "$item"
    while [ ! -d "$test" ]
    do
        last="$test"
        test=$(dirname "$test")
        [ -z "$test" ] && break
    done
    if [ -n "$last" ]
    then
        explain "$test"
        explain "$last"
    else
        printf "\t%s: ok\n" "$item"
    fi
done
Thomas Dickey
fuente
Se supone que los argumentos están destinados a ser directorios (o enlaces simbólicos a directorios).
Stéphane Chazelas
1
si solo cd not_a_directorytu caparazón solo escribirá para stderr algo así cd:cd:6: no such file or directory: not_a_directory. En realidad, el shell del usuario lo hará en un formato con el que el usuario probablemente ya esté muy familiarizado. Casi siempre es más fácil y mejor al final de todos modos hacer cosas y dejar que el shell maneje los informes según sea necesario. Sin embargo, ese tipo de filosofía requiere una atención muy estricta a los valores de retorno y la promoción / preservación de los mismos.
mikeserv
1

Solo una solución alternativa para bash, suponiendo que la ruta es una ruta absoluta (comienza con /):

#!/bin/bash

pathname="$1"

IFS='/' read -r -a p <<<"${pathname#/}"

pa=""    max="${#p[@]}"    i=0
while (( i<"$max" )); do
      pa="$pa/${p[i++]}"
      if     [[ ! -e $pa ]]; then
             printf 'failed at: \t"%s"\t"%s"\n' "${pa##*/}" "${pa}"
             break
      fi
done

$ ./script "/foo/ba r/baz/hello/world"
failed at:      "hello"        "/foo/ba r/baz/hello"

fuente
Esto funcionó para mí. Necesitaba la última ruta válida en la cadena de ruta, así que salvo paque pa_prevcomo la primera línea del bucle while (antes de que se incrementa). Cuando la prueba para "pa" falla, pa_prevtiene el último directorio existente en la ruta dada.
Ville
0
(( dirct=$(echo ${dir}|tr "/" " "|wc -w)+1 ))
i=2
while [ ${i} -le ${dirct} ]
do
  sdir=$(echo ${dir}|cut -d/ -f1,${i})
  if [ ! -d ${sdir} ]
  then
    echo "Path is broken at ${sdir}"
  fi
  (( i++ ))
done

no es simple, pero puede ponerlo en un script, hacerlo ejecutable y pegarlo en algún lugar de su camino, si lo va a usar con frecuencia.

Advertencia: si el nombre de su directorio en cualquier nivel contiene un spacecarácter, esto NO funcionará.

MelBurslan
fuente
¿Es este código bash? Tuve que cambiar $ {dir} estaba en blanco, así que cambié en $ 1, pero ahora sdir está en blanco para mí.
kuzzooroo