Jak znaleźć pierwszy brakujący katalog na długiej ścieżce?

14

Wyobraź sobie, że mam ścieżkę, która nie istnieje:

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

Ale powiedzmy /foo/bar nie istnieje. Czy mogę szybko ustalić, czy bazjest to punkt krytyczny na ścieżce?

Używam Bash.

kuzzooroo
źródło
access(2)nie jest zbyt szczegółowe, więc rozwiązanie zazwyczaj polega na napisaniu czegoś do iteracji i przetestowaniu kolejno każdego elementu ścieżki ...
thrig

Odpowiedzi:

5

Biorąc pod uwagę kanoniczną nazwę ścieżki, taką jak Twoja, zadziała to:

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

Spowoduje to wydrukowanie ostatniego w pełni istniejącego / dostępnego komponentu $pathnamei umieszczenie każdego z nich osobno w tablicy arg. Pierwszy nieistniejący składnik nie jest drukowany, ale jest zapisywany $p.

Możesz podejść do tego przeciwnie:

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

To albo wróci odpowiednio, albo w $pathrazie potrzeby zmniejszy koszty . Odmawia próby zmiany /, ale jeśli się powiedzie, wydrukuje zarówno bieżący katalog roboczy, jak i katalog, w którym zmienia się na standardowe wyjście. Twój prąd również $PWDzostanie włączony $OLDPWD.

mikeserv
źródło
Oczywiście: for p in $pathnamebędą podlegały „dzieleniu słów” i „rozwijaniu nazw ścieżek”.
1
@BinaryZebra - tak, jest podzielony na $IFS. dokładnie tak to działa. nie podlega rozszerzaniu nazw ścieżek, z tym wyjątkiem, że zmienna tutaj nazywana $pathnamejest rozwijana do tablicy składników ścieżek w podziale $IFS.
mikeserv
$pathnameset -f
@BinaryZebra - niczego nie unikam. Im określam środowisko, którego potrzebuję, aby zrobić to solidnie. to wszystko. nie - nie powrócił do wartości domyślnej i nie zainstaluje się na komputerze pytającego ani nie zrobi mi śniadania.
mikeserv
1
@BinaryZebra - jeśli tak, jak powinien każdy dobry programista, często martwisz się, że niepotrzebnie wpłyniesz na / odzyskasz swoje środowisko wykonawcze, możesz zainteresować się tym ns, co, jeśli zostanie użyte tutaj, sprawi, że dyskusja stanie się dyskusyjna.
mikeserv
12

Jednym z moich ulubionych programów jest nameiczęść util-linuxi stąd ogólnie obecna tylko w Linuksie:

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

Ale jego wyniki nie są bardzo analizowalne. Więc jeśli chcesz tylko wskazać, że czegoś brakuje, nameimoże być przydatne.

Przydaje się to do rozwiązywania ogólnych problemów z dostępem do ścieżki, ponieważ można ustawić, czy komponent jest linkiem czy punktem podłączenia, a także uprawnienia:

$ 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

Stolica Dwskazuje punkt montowania.

muru
źródło
@ StéphaneChazelas Miałem nadzieję, że odpowiedniki mogą być obecne w innych systemach. Nic dla BSD?
muru
nameidziała dla mnie tak długo, jak jest to kanał, który istnieje, ale gdy dam mu taki, którego nie dostanę namei: failed to stat: /usr/share/foo/bar: No such file or directory.
kuzzooroo
@kuzzooroo która wersja namei?
muru
moja wersja namei nie podaje wersji w swojej pomocy ani na stronie man i nie obsługuje flagi podającej informacje o wersji. Jestem w stanie stwierdzić, że pochodzi on z pakietu linux-utils 2.16-1ubuntu5, ale nie mogę zrozumieć, co to oznacza dla wersji namei.
kuzzooroo,
@kuzzooroo Ponieważ nawet Ubuntu 12.04 ma wersję 2.20.1 , musisz być na bardzo starej wersji Ubuntu. 10.04?
muru
1

Coś w tym stylu (uwzględnianie ścieżek z osadzonymi odstępami):

#!/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
źródło
Zakłada, że ​​argumentami są katalogi (lub dowiązania symboliczne do katalogów).
Stéphane Chazelas
1
jeśli tylko cd not_a_directorytwoja powłoka napisze do stderr coś w rodzaju cd:cd:6: no such file or directory: not_a_directory. W rzeczywistości powłoka użytkownika zrobi to w formacie, z którym użytkownik prawdopodobnie już jest bardzo dobrze zaznajomiony. W końcu prawie zawsze jest to łatwiejsze i lepsze, aby po prostu robić różne rzeczy i pozwolić powłoce obsługiwać raportowanie w razie potrzeby. Tego rodzaju filozofia wymaga jednak bardzo ścisłej uwagi na temat zwracanych wartości oraz ich promowania / zachowania.
mikeserv
1

Tylko alternatywne rozwiązanie dla bash, zakładając, że ścieżka jest ścieżką absolutną (zaczyna się od /):

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

źródło
To zadziałało dla mnie. Potrzebowałem ostatniej poprawnej ścieżki w ciągu ścieżki, więc zapisałem ją pajako pa_prevpierwszą linię pętli while (zanim zostanie zwiększona). Gdy test „pa” nie powiedzie się, pa_prevma ostatni istniejący katalog w podanej ścieżce.
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

nie jest to proste, ale możesz umieścić go w skrypcie, sprawić, by był wykonywalny i umieścić go gdzieś na swojej ścieżce, jeśli będziesz go często używać.

Zastrzeżenie emptor: Jeśli nazwa katalogu na dowolnym poziomie zawiera spaceznak, to NIE zadziała.

MelBurslan
źródło
Czy to jest kod bashowy? Musiałem zamienić $ {dir} nadchodziło puste, więc zamieniłem 1 $, ale teraz sdir jest dla mnie pusty.
kuzzooroo