Как я могу найти первый отсутствующий каталог в длинном пути?

14

Представьте, что у меня есть путь, которого не существует:

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

Но скажем /foo/bar , существует. Есть ли быстрый способ для меня, чтобы определить, что bazявляется переломным моментом на пути?

Я использую Bash.

kuzzooroo
источник
access(2)не очень детализирован, поэтому решение обычно включает в себя написание чего-то для итерации и проверки каждого элемента пути по очереди ...
thrig

Ответы:

5

Учитывая канонический путь, такой как ваш, это будет работать:

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

Он печатает последний полностью существующий / доступный компонент $pathnameи помещает каждый из них отдельно в массив arg. Первый несуществующий компонент не печатается, но сохраняется в $p.

Вы можете подойти к этому противоположно:

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

Это либо вернется соответственно, либо уменьшится по $pathмере необходимости. Он отклоняет попытку изменения /, но в случае успеха напечатает как ваш текущий рабочий каталог, так и каталог, в который он переходит на стандартный вывод. Ваш ток $PWDбудет также введен $OLDPWD.

mikeserv
источник
Понятно: for p in $pathnameбудут разделены слова на разделение и расширение пути.
1
@BinaryZebra - да, это разделено на $IFS. это именно то, как это работает. он не подлежит расширению пути, за исключением того, что вызываемая здесь переменная $pathnameрасширяется до массива компонентов пути, разделенных на $IFS.
mikeserv
Вы избегаете «Расширение пути» в var $pathname с помощью set -f«, который не возвращается к значению по умолчанию.
@BinaryZebra - я ничего не избегаю. Я указываю среду, в которой я нуждаюсь, чтобы сделать это надежно. это все. нет - он не вернется к значению по умолчанию, и при этом он не установится на компьютер аскера и не заставит меня позавтракать.
mikeserv
1
@BinaryZebra - кстати, если, как любой хороший программист, вам часто приходится беспокоиться о неоправданном влиянии / восстановлении среды выполнения, вас может заинтересовать ns, что, если оно будет использовано здесь, сделает это обсуждение спорным.
mikeserv
12

Одна из моих любимых утилит является nameiчастью util-linuxи, следовательно, обычно присутствует только в Linux:

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

Но его вывод не очень разбирается. Так что, если вы просто хотите указать, что чего-то не хватает, это nameiможет быть полезно.

Это полезно для устранения общих проблем с доступом к пути, поскольку вы можете указать, является ли компонент ссылкой или точкой монтирования, а также его разрешениями:

$ 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

Столица Dуказывает точку монтирования.

Мур
источник
@ StéphaneChazelas Я надеялся, что эквиваленты могут присутствовать в других системах. Ничего для BSD?
Муру
nameiработает для меня до тех пор, пока он подает путь, который существует, но когда я даю ему путь, которого нет, я не получаю namei: failed to stat: /usr/share/foo/bar: No such file or directory.
kuzzooroo
@kuzzooroo, какая версия namei?
Муру
Моя версия namei не содержит ни версии, ни справки, ни справочной страницы и не поддерживает флаг, сообщающий информацию о версии. Я могу определить, что это из пакета linux-utils 2.16-1ubuntu5, но не могу понять, что это означает для версии namei.
kuzzooroo
@kuzzooroo Поскольку даже в Ubuntu 12.04 есть версия 2.20.1 , вы действительно должны использовать очень старую версию Ubuntu. 10,04?
Муру
1

Примерно так (с учетом путей к файлам со встроенными пробелами):

#!/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
Томас Дики
источник
Предполагается, что аргументы должны быть каталогами (или символическими ссылками на каталоги).
Стефан Шазелас
1
если вы просто cd not_a_directoryваша оболочка просто напишите в stderr что-то вроде cd:cd:6: no such file or directory: not_a_directory. На самом деле, оболочка пользователя будет делать это в формате, с которым пользователь, вероятно, уже очень хорошо знаком. В конце концов, это почти всегда и легче, и лучше, в конце концов, просто делать вещи и позволить оболочке обрабатывать отчеты по мере необходимости. Такая философия, тем не менее, требует очень строгого внимания к возвращаемым ценностям и их продвижению / сохранению.
mikeserv
1

Просто альтернативное решение для bash, предполагая, что путь является абсолютным (начинается с /):

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

источник
Это сработало для меня. Мне нужен был последний допустимый путь в строке пути, поэтому я сохранил paкак pa_prevпервую строку цикла while (до того, как он будет увеличен). Когда проверка для «pa» не проходит, pa_prevимеет последний существующий каталог по указанному пути.
Вилле
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

это не просто, но вы можете поместить его в скрипт, сделать его исполняемым и вставить его где-нибудь на вашем пути, если вы собираетесь использовать его часто.

Предостережение: если имя вашего каталога на любом уровне содержит spaceсимвол, это НЕ будет работать.

MelBurslan
источник
Это код Bash? Мне пришлось поменять местами в $ {dir}, поэтому я поменялся на $ 1, но теперь для меня пустует sdir.
kuzzooroo