encontre a pesquisa nos diretórios pai em vez de subdiretórios


45

Estou aninhado em uma árvore de arquivos e gostaria de descobrir qual diretório pai contém um arquivo.

Por exemplo, estou em um conjunto de repositórios Git aninhados e quero encontrar o diretório .git que controla os arquivos em que estou atualmente. Eu espero por algo como find -searchup -iname ".git"

Respostas:


16

Uma versão ainda mais geral que permite o uso de findopções:

#!/bin/bash
set -e
path="$1"
shift 1
while [[ $path != / ]];
do
    find "$path" -maxdepth 1 -mindepth 1 "$@"
    # Note: if you want to ignore symlinks, use "$(realpath -s "$path"/..)"
    path="$(readlink -f "$path"/..)"
done

Por exemplo (supondo que o script seja salvo como find_up.sh)

find_up.sh some_dir -iname "foo*bar" -execdir pwd \;

... imprimirá os nomes de todos some_diros ancestrais (incluindo ele próprio) até os /quais um arquivo com o padrão seja encontrado.

Ao usar readlink -fo script acima, siga os links simbólicos no caminho, conforme observado nos comentários. Em realpath -svez disso, você pode usar se desejar seguir os caminhos pelo nome ("/ foo / bar" aumentará para "foo" mesmo que "bar" seja um link simbólico) - no entanto, isso requer instalação realpathque não é instalada por padrão em maioria das plataformas.


o problema com isso é a maneira como resolve os links. por exemplo, se eu estiver em ~ / foo / bar e bar for um link simbólico para / some / other / place, então ~ / foo e ~ / nunca serão pesquisados.
— Erik Aronesty

@ ErikAronesty, você está certo - atualizou a resposta. Você pode usar realpath -spara obter o comportamento que deseja.
— sinelaw

isso funciona quando você especifica um caminho completo para pesquisar. infelizmente, no centos 5.8, o realpath -s <dir> possui um bug onde se <dir> é relativo, então resolve links simbólicos de qualquer maneira ... apesar da documentação.
— Erik Aronesty

readlinknão faz parte do padrão. Um script portátil pode ser implementado apenas com recursos de shell POSIX.
— schily

1
FYI, isso não funciona no zsh porque redefine $ path para que o shell não possa encontrar findou readlink. Sugiro alterá-lo para algo como $curpathisso, para que não oculte a variável shell.
— Skyler

28
git rev-parse --show-toplevel

imprimirá o diretório de nível superior do repositório atual, se você estiver em um.

Outras opções relacionadas:

# `pwd` is inside a git-controlled repository
git rev-parse --is-inside-work-tree
# `pwd` is inside the .git directory
git rev-parse --is-inside-git-dir

# path to the .git directory (may be relative or absolute)
git rev-parse --git-dir

# inverses of each other:
# `pwd` relative to root of repository
git rev-parse --show-prefix
# root of repository relative to `pwd`
git rev-parse --show-cdup

4
Isso funciona apenas para o próprio git. A questão é mais genérica.
— alex

1
Isso não vai funcionar em um repo nua,
— Jason Pyeron

19

Uma versão generalizada da resposta de Gilles, primeiro parâmetro usado para encontrar correspondência:

find-up () {
  path=$(pwd)
  while [[ "$path" != "" && ! -e "$path/$1" ]]; do
    path=${path%/*}
  done
  echo "$path"
}

Mantém o uso de links simbólicos.


15

Encontrar não pode fazer isso. Não consigo pensar em nada mais simples do que um loop de shell. (Não testado, assume que não há /.git)

git_root=$(pwd -P 2>/dev/null || command pwd)
while [ ! -e "$git_root/.git" ]; do
  git_root=${git_root%/*}
  if [ "$git_root" = "" ]; then break; fi
done

Para o caso específico de um repositório git, você pode deixar o git fazer o trabalho por você.

git_root=$(GIT_EDITOR=echo git config -e)
git_root=${git_root%/*}

1
Legal, isso me coloca no caminho de uma solução geral - que eu posto como outra resposta aqui.
— Vincent Scheib

12

Se você estiver usando o zsh com o globbing estendido ativado, poderá fazê-lo com um oneliner:

(../)#.git(:h)   # relative path to containing directory, eg. '../../..', '.'
(../)#.git(:a)   # absolute path to actual file, eg. '/home/you/src/prj1/.git'
(../)#.git(:a:h) # absolute path to containing directory, eg. '/home/you/src/prj1'

Explicação (citada em man zshexpn):

Globbing Recursivo

Um componente de nome de caminho do formulário (foo/)#corresponde a um caminho que consiste em zero ou mais diretórios correspondentes ao padrão foo. Como abreviação, **/é equivalente a (*/)#.

Modificadores

Após o designador opcional da palavra, você pode adicionar uma sequência de um ou mais dos seguintes modificadores, cada um precedido por um ':'. Esses modificadores também funcionam no resultado da geração do nome do arquivo e da expansão dos parâmetros, exceto onde indicado.

  • uma
    • Transforme um nome de arquivo em um caminho absoluto: acrescenta o diretório atual, se necessário, e resolve qualquer uso de '..' e '.'
  • UMA
    • Como ' a ', mas também resolva o uso de links simbólicos sempre que possível. Observe que a resolução de '..' ocorre antes da resolução dos links simbólicos. Essa chamada é equivalente a a menos que o seu sistema tenha a realpathchamada do sistema (os sistemas modernos têm).
  • h
    • Remova um componente de nome de caminho à direita, deixando a cabeça. Isso funciona como ' dirname'.

Créditos: Fauxon #zshpara a sugestão inicial de usar (../)#.git(:h).


Isso é incrível ... Se eu pudesse descobrir (dica: você deveria me dizer ) o que (../)está fazendo ... Ah, e quem / o que é Faux on #zsh?
— 21815 alex alex Gray

1
O @alexgray por (../)si só não significa muito, mas (../)#significa tentar expandir o padrão entre parênteses 0 ou mais vezes e usar apenas os que realmente existem no sistema de arquivos. Como estamos expandindo de 0 para n diretórios pai, pesquisamos efetivamente para cima até a raiz do sistema de arquivos (nota: você provavelmente deseja adicionar algo após o padrão para torná-lo significativo, mas para a execução da iluminação print -l (../)#). E #zshé um canal de IRC, Fauxé um nome de usuário nele. Fauxsugeriu a solução, portanto o crédito.
— impensado

1
Isso irá expandir todos eles. Você pode querer: (../)#.git(/Y1:a:h)parar no primeiro encontrado (com uma versão recente do zsh)
— Stéphane Chazelas

1
Muito útil, obrigado. Para habilitar estendido Do englobamentosetopt extended_glob
— Shlomi

0

Esta versão do findup suporta a sintaxe "find", como a resposta do @ sinelaw, mas também suporta links simbólicos sem precisar de caminho real. Ele também suporta um recurso "parar em" opcional, portanto, isso funciona: findup .:~ -name foo... procura por foo sem passar o diretório inicial.

#!/bin/bash

set -e

# get optional root dir
IFS=":" && arg=($1)
shift 1
path=${arg[0]}
root=${arg[1]}
[[ $root ]] || root=/
# resolve home dir
eval root=$root

# use "cd" to prevent symlinks from resolving
cd $path
while [[ "$cur" != "$root" && "$cur" != "/" ]];
do
    cur="$(pwd)"
    find  "$cur/"  -maxdepth 1 -mindepth 1 "$@"
    cd ..
done

0

Descobri que trabalhar com links simbólicos mata algumas das outras opções. Especialmente as respostas específicas do git. Na metade, criei meu próprio favorito a partir dessa resposta original, que é bastante eficaz.

#!/usr/bin/env bash

# usage: upsearch .git

function upsearch () {
    origdir=${2-`pwd`}
    test / == "$PWD" && cd "$origdir" && return || \
    test -e "$1" && echo "$PWD" && cd "$origdir" && return || \
    cd .. && upsearch "$1" "$origdir"
}

Estou usando links simbólicos para meus projetos go porque go quer código fonte em um determinado local e eu gosto de manter meus projetos em ~ / projects. Crio o projeto em $ GOPATH / src e os vinculo a ~ / projects. Portanto, a execução git rev-parse --show-toplevelimprime o diretório $ GOPATH, não o diretório ~ / projects. Esta solução resolve esse problema.

Sei que é uma situação muito específica, mas acho que a solução é valiosa.


0

A solução de Vincent Scheib não funciona para arquivos que residem no diretório raiz.

A versão a seguir faz e também permite passar o diretório inicial como o 1º argumento.

find-up() {
    path="$(realpath -s "$1")"

    while ! [ -e "$path"/"$2" ] && [ -n "$path" ]; do
        path="${path%/*}"
    done

    [ -e "$path"/"$2" ] && echo "$path"/"$2"
}

0

Eu modifiquei a solução do sinelaw para ser POSIX. Ele não segue links simbólicos e inclui a pesquisa no diretório raiz usando um loop do while.

find-up:
#!/usr/bin/env sh

set -e # exit on error
# Use cd and pwd to not follow symlinks.
# Unlike find, default to current directory if no arguments given.
directory="$(cd "${1:-.}"; pwd)"

shift 1 # shift off the first argument, so only options remain

while :; do
  # POSIX, but gets "Permission denied" on private directories.
  # Use || true to allow errors without exiting on error.
  find "$directory" -path "${directory%/}/*/*" -prune -o ! -path "$directory" "$@" -print || true

  # Equivalent, but -mindepth and -maxdepth aren't POSIX.
  # find "$directory" -mindepth 1 -maxdepth 1 "$@"

  if [ "$directory" = '/' ]; then
    break # End do while loop
  else
    directory=$(dirname "$directory")
  fi
done
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.