buscar en directorios principales en lugar de subdirectorios


45

Estoy anidado en lo profundo de un árbol de archivos, y me gustaría encontrar qué directorio principal contiene un archivo.

Por ejemplo, estoy en un conjunto de repositorios Git anidados y quiero encontrar el directorio .git que controla los archivos en los que estoy actualmente. Esperaría algo como find -searchup -iname ".git"

Respuestas:


16

Una versión aún más general que permite usar findopciones:

#!/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 ejemplo (suponiendo que el script se guarda como find_up.sh)

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

... imprimirá los nombres de todos some_dirlos antepasados ​​(incluido él mismo) hasta /que se encuentre un archivo con el patrón.

Cuando utilice readlink -fel script anterior, seguirá los enlaces simbólicos en el camino, como se señala en los comentarios. En su realpath -slugar, puede usar , si desea seguir rutas por nombre ("/ foo / bar" irá a "foo" incluso si "bar" es un enlace simbólico); sin embargo, eso requiere una instalación realpathque no está instalada de forma predeterminada en La mayoría de las plataformas.


El problema con estos es la forma en que resuelve los enlaces. por ejemplo, si estoy en ~ / foo / bar, y bar es un enlace simbólico a / some / other / place, entonces ~ / foo y ~ / nunca se buscarán.
Erik Aronesty

@ ErikAronesty, tienes razón: actualizó la respuesta. Puede usar realpath -spara obtener el comportamiento que desea.
sinelaw

eso funciona cuando especifica una ruta completa para buscar. lamentablemente, en centos 5.8, realpath -s <dir> tiene un error donde si <dir> es relativo, entonces resuelve los enlaces simbólicos de todos modos ... a pesar de su documentación.
Erik Aronesty

readlinkNo es parte de la norma. Un script portátil podría implementarse solo con las funciones de shell POSIX.
schily

1
FYI, esto no funciona en zsh porque redefine $ path para que el shell no pueda encontrar findo readlink. Sugiero cambiarlo a algo así $curpathpara que no sombree la variable de shell.
Skyler

28
git rev-parse --show-toplevel

imprimirá el directorio de nivel superior del repositorio actual, si está en uno.

Otras opciones 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

44
Esto funciona solo para git en sí. La pregunta es más genérica.
alex

1
Esto no funcionará en un repositorio desnudo,
Jason Pyeron

19

Una versión generalizada de la respuesta de Gilles, primer parámetro utilizado para encontrar coincidencia:

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

Mantiene el uso de enlaces simbólicos.


15

Find no puede hacerlo. No puedo pensar en nada más simple que un bucle de shell. (No probado, asume que no hay /.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 el caso específico de un repositorio de git, puede dejar que git haga el trabajo por usted.

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

1
Genial, esto me pone en el camino de una solución general, que publico como otra respuesta aquí.
Vincent Scheib

12

Si está usando zsh con el globbing extendido habilitado, puede hacerlo con un 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'

Explicación (citado de man zshexpn):

Globbing recursivo

Un componente de nombre de ruta del formulario (foo/)#coincide con una ruta que consta de cero o más directorios que coinciden con el patrón foo. Como taquigrafía, **/es equivalente a (*/)#.

Modificadores

Después del designador de palabras opcional, puede agregar una secuencia de uno o más de los siguientes modificadores, cada uno precedido por un ':'. Estos modificadores también funcionan en el resultado de la generación de nombre de archivo y la expansión de parámetros, excepto donde se indique.

  • una
    • Convierta un nombre de archivo en una ruta absoluta: antepone el directorio actual, si es necesario, y resuelve cualquier uso de '..' y '.'
  • UNA
    • Como ' a ', pero también resuelve el uso de enlaces simbólicos cuando sea posible. Tenga en cuenta que la resolución de '..' ocurre antes de la resolución de enlaces simbólicos. Esta llamada es equivalente a una a menos que su sistema tenga la realpathllamada del sistema (los sistemas modernos la tienen).
  • h
    • Elimine un componente de nombre de ruta final, dejando la cabeza. Esto funciona como ' dirname'.

Créditos: Fauxen #zshla sugerencia inicial de uso (../)#.git(:h).


Esto es increíble ... Si solo pudiera entender (pista: deberías decirme ) qué (../)está haciendo ... Ah, ¿y quién / qué es Faux on #zsh?
alex gray

1
@alexgray (../)por sí solo no significa mucho, pero (../)#significa tratar de expandir el patrón dentro del paréntesis 0 o más veces, y solo usar los que realmente existen en el sistema de archivos. Debido a que estamos expandiendo de 0 a n directorios principales, buscamos efectivamente hacia arriba a la raíz del sistema de archivos (nota: probablemente desee agregar algo después del patrón para que sea significativo, pero para la ejecución de la iluminación print -l (../)#). Y #zshes un canal IRC, Fauxes un nombre de usuario en él. Fauxsugirió la solución, por lo tanto, el crédito.
pensado

1
Eso los expandirá a todos. Es posible que desee: (../)#.git(/Y1:a:h)detenerse en el primero encontrado (con una versión reciente de zsh)
Stéphane Chazelas

1
Muy servicial, gracias. Para habilitar el globbing extendido hacersetopt extended_glob
Shlomi

0

Esta versión de findup admite la sintaxis "find", como la respuesta de @ sinelaw, pero también admite enlaces simbólicos sin necesidad de realpath. También admite una función opcional "detenerse en", por lo que funciona: findup .:~ -name foo... busca foo sin pasar el directorio de inicio.

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

Descubrí que trabajar con enlaces simbólicos elimina algunas de las otras opciones. Especialmente las respuestas específicas de git. A mitad de camino he creado mi propio favorito de esta respuesta original que es bastante eficiente.

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

Estoy usando enlaces simbólicos para mis proyectos go porque go quiere el código fuente en una ubicación determinada y me gusta mantener mis proyectos en ~ / projects. Creo el proyecto en $ GOPATH / src y los enlace simbólicamente a ~ / projects. Entonces, la ejecución git rev-parse --show-toplevelimprime el directorio $ GOPATH, no el directorio ~ / projects. Esta solución resuelve ese problema.

Me doy cuenta de que esta es una situación muy específica, pero creo que la solución es valiosa.


0

La solución de Vincent Scheib no funciona para archivos que residen en el directorio raíz.

La siguiente versión sí, y también le permite pasar el directorio inicial como el primer argumento.

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

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

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

0

He modificado la solución de sinelaw ser POSIX. No sigue los enlaces simbólicos e incluye la búsqueda en el directorio raíz mediante un bucle 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
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.