Como posso encontrar o primeiro diretório ausente em um longo caminho?

14

Imagine que eu tenho um caminho que não existe:

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

Mas digamos /foo/bar que exista. Existe uma maneira rápida de determinar que esse bazé o ponto de ruptura no caminho?

Estou usando o Bash.

kuzzooroo
fonte
access(2)não é muito granular, então a solução geralmente envolve escrever algo para iterate e testar cada elemento do caminho, por sua vez ...
thrig

Respostas:

5

Dado um nome de caminho canônico, como o seu, isso funcionará:

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

Isso imprime através do último componente totalmente existente / acessível de $pathnamee coloca cada um deles separadamente na matriz arg. O primeiro componente inexistente não é impresso, mas é salvo em $p.

Você pode abordá-lo de maneira oposta:

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

Isso retornará adequadamente ou diminuirá $pathconforme necessário. Ele se recusa a tentar alterar /, mas, se for bem-sucedido, imprimirá seu diretório de trabalho atual e o diretório para o qual ele muda para stdout. Sua corrente $PWDtambém será inserida $OLDPWD.

mikeserv
fonte
Claramente: for p in $pathnameestará sujeito a "divisão de palavras" e "expansão do nome do caminho".
1
@BinaryZebra - sim, está dividido $IFS. é exatamente assim que funciona. ela não está sujeita à expansão do nome do caminho, exceto que a variável aqui chamada $pathnameé expandida para uma matriz de componentes do caminho como dividida $IFS.
mikeserv
Você está evitando a "expansão de nome" na var $pathname usando set -f", que não é devolvido ao seu valor padrão.
@BinaryZebra - não estou evitando nada. estou especificando o ambiente necessário para fazer isso com robustez. Isso é tudo. não - não retornou ao seu valor padrão e nem se instalará no computador do solicitante ou fará o café da manhã.
mikeserv
1
@BinaryZebra - by by, se, como qualquer bom programador, você frequentemente se preocupa em afetar / recuperar desnecessariamente seu ambiente de execução, você pode se interessar pelo nsque, se usado aqui, tornaria essa discussão discutível.
mikeserv
12

Um dos meus utilitários favoritos é nameiparte util-linuxe, portanto, geralmente está presente apenas no Linux:

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

Mas sua produção não é muito parseable. Portanto, se você deseja apenas salientar que algo está faltando, nameipode ser útil.

É útil para solucionar problemas gerais ao acessar um caminho, pois você pode indicar se um componente é um link ou um ponto de montagem, bem como suas permissões:

$ 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

A capital Dindica um ponto de montagem.

muru
fonte
@ StéphaneChazelas Eu esperava que equivalentes estivessem presentes em outros sistemas. Nada para os BSDs?
muru
nameifunciona para mim desde que o alimente, um caminho que existe, mas quando dou um que não recebo namei: failed to stat: /usr/share/foo/bar: No such file or directory.
22416 kiffooroo
@kuzzooroo qual versão do namei?
muru
minha versão do namei não fornece uma versão em sua ajuda nem em sua página de manual e não suporta um sinalizador que fornece informações sobre a versão. Eu sou capaz de determinar que ele vem do pacote linux-utils 2.16-1ubuntu5, mas não consigo descobrir o que isso implica para a versão do namei.
precisa saber é o seguinte
@kuzzooroo Como até o Ubuntu 12.04 possui a 2.20.1 , você deve estar em uma versão muito antiga do Ubuntu. 10.04?
muru
1

Algo assim (responsável por nomes de caminho com espaços em branco incorporados):

#!/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
fonte
Ele assume que os argumentos devem ser diretórios (ou links simbólicos para diretórios).
Stéphane Chazelas
1
se você apenas o cd not_a_directoryseu shell apenas escrever para stderr algo como cd:cd:6: no such file or directory: not_a_directory. Na verdade, o shell do usuário fará isso em um formato com o qual o usuário provavelmente já esteja muito familiarizado. É quase sempre mais fácil e melhor, no final das contas, fazer as coisas e deixar o shell manipular os relatórios conforme necessário. Esse tipo de filosofia, no entanto, exige uma atenção muito rigorosa para retornar valores e a promoção / preservação dos mesmos.
mikeserv
1

Apenas uma solução alternativa para o bash, assumindo que o caminho é um caminho absoluto (começa com /):

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

fonte
Isso funcionou para mim. Eu precisava o último caminho válido na cadeia de caminho, então eu salvo pacomo pa_prevcomo a primeira linha do loop while (antes que seja incrementado). Quando o teste para "pa" falhar, pa_prevo último diretório existente no caminho especificado.
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

não é simples, mas você pode colocá-lo em um script, torná-lo executável e colá-lo em algum lugar do seu caminho, se você o usar frequentemente.

Advertência: Se o nome do diretório em qualquer nível contiver um spacecaractere, isso NÃO funcionará.

MelBurslan
fonte
É este código bash? Eu tive que trocar $ {dir} em branco, então troquei $ 1, mas agora o sdir está em branco para mim.
precisa saber é o seguinte