Comment puis-je trouver le premier répertoire manquant dans un long chemin?

14

Imaginez que j'ai un chemin qui n'existe pas:

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

Mais disons /foo/bar qu'il existe. Existe-t-il un moyen rapide pour moi de déterminer que bazc'est le point de rupture du chemin?

J'utilise Bash.

kuzzooroo
la source
access(2)n'est pas très granulaire, donc la solution consiste généralement à écrire quelque chose pour itérer et tester chaque élément de chemin à son tour ...
thrig

Réponses:

5

Étant donné un chemin d'accès canonique, tel que le vôtre, cela fonctionnera:

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

Cela imprime à travers le dernier composant entièrement existant / accessible de $pathname, et place chacun de ceux-ci séparément dans le tableau arg. Le premier composant inexistant n'est pas imprimé, mais il est enregistré dans $p.

Vous pourriez l'approcher à l'opposé:

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

Cela reviendra de façon appropriée ou se réduira $pathau besoin. Il refuse de tenter une modification vers /, mais en cas de succès, il imprime à la fois votre répertoire de travail actuel et le répertoire dans lequel il se transforme en stdout. Votre courant $PWDsera également enregistré $OLDPWD.

mikeserv
la source
En clair: for p in $pathnamesera soumis à la "division des mots" et à "l'expansion du nom de chemin"
1
@BinaryZebra - oui, il est partagé $IFS. c'est exactement comme ça que ça marche. il n'est pas soumis à l'expansion du nom de chemin, sauf que la variable appelée ici $pathnameest étendue à un tableau de composants de chemin comme partagé $IFS.
mikeserv
Vous évitez l '"extension de chemin d'accès" sur var $pathname en utilisant set -f"qui n'est pas retourné à sa valeur par défaut.
@BinaryZebra - je n'évite rien. Im spécifiant l'environnement dont j'ai besoin pour le faire de manière robuste. c'est tout. non - ce n'est pas revenu à sa valeur par défaut et il ne s'installera pas non plus sur l'ordinateur du demandeur ou ne me fera pas de petit déjeuner.
mikeserv
1
@BinaryZebra - au fait, si, comme tout bon programmeur devrait, vous vous inquiétez souvent d'affecter / de récupérer inutilement votre environnement d'exécution, vous pourriez vous intéresser à nsce qui, s'il était utilisé ici, rendrait cette discussion théorique.
mikeserv
12

L'une de mes utilités préférées est namei, faisant partie de util-linuxet donc généralement présente uniquement sur Linux:

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

Mais sa sortie n'est pas très analysable. Donc, si vous souhaitez simplement signaler qu'il manque quelque chose, cela nameipourrait être utile.

Il est utile pour résoudre les problèmes généraux d'accès à un chemin, car vous pouvez lui faire indiquer si un composant est un lien ou un point de montage, ainsi que ses autorisations:

$ 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 capitale Dindique un point de montage.

muru
la source
@ StéphaneChazelas J'espérais que des équivalents pourraient être présents sur d'autres systèmes. Rien pour les BSD?
muru
nameifonctionne pour moi tant qu'un flux lui donne un chemin qui existe, mais quand je lui en donne un que je ne reçois pas namei: failed to stat: /usr/share/foo/bar: No such file or directory.
kuzzooroo
@kuzzooroo quelle version de namei?
muru
ma version de namei ne donne pas de version dans son aide ni sa page de manuel et ne prend pas en charge un indicateur donnant des informations de version. Je suis en mesure de déterminer qu'il provient du paquet linux-utils 2.16-1ubuntu5 mais je ne peux pas comprendre ce que cela implique pour la version de namei.
kuzzooroo
@kuzzooroo Puisque même Ubuntu 12.04 a 2.20.1 , vous devez en effet être sur une très ancienne version d'Ubuntu. 10.04?
muru
1

Quelque chose comme ça (en tenant compte des chemins d'accès avec des blancs intégrés):

#!/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
la source
Il suppose que les arguments sont censés être des répertoires (ou des liens symboliques vers des répertoires).
Stéphane Chazelas
1
si vous venez d' cd not_a_directoryécrire à votre shell quelque chose comme stderr cd:cd:6: no such file or directory: not_a_directory. En fait, le shell de l'utilisateur le fera dans un format avec lequel l'utilisateur est probablement déjà très familier. Il est presque toujours à la fois plus facile et meilleur à la fin de faire des choses et de laisser le shell gérer les rapports si nécessaire. Ce type de philosophie nécessite cependant une attention très stricte aux valeurs de retour et à leur promotion / préservation.
mikeserv
1

Juste une solution alternative pour bash, en supposant que le chemin est un chemin absolu (commence par /):

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

la source
Cela a fonctionné pour moi. J'avais besoin du dernier chemin valide dans la chaîne de chemin, j'ai donc enregistré en patant pa_prevque première ligne de la boucle while (avant qu'elle ne soit incrémentée). Lorsque le test de "pa" échoue, pa_prevle dernier répertoire existant se trouve dans le chemin d'accès indiqué.
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

ce n'est pas simple mais vous pouvez le mettre dans un script, le rendre exécutable et le coller quelque part sur votre chemin, si vous allez l'utiliser fréquemment.

Caveat emptor: Si votre nom de répertoire à n'importe quel niveau contient un spacecaractère, cela ne fonctionnera PAS.

MelBurslan
la source
Est-ce le code bash? J'ai dû échanger dans $ {dir} était vide donc j'ai échangé dans $ 1, mais maintenant sdir est vide pour moi.
kuzzooroo