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"

Vincent Scheib
fonte

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.

sinelaw
fonte
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
efémero
fonte
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.

Vincent Scheib
fonte
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%/*}
Gilles 'SO- parar de ser mau'
fonte
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).

impensado
fonte
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
Erik Aronesty
fonte
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.

blockloop
fonte
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"
}
Fabio A.
fonte
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
dosentmatter
fonte