rechercher dans les répertoires parents au lieu de sous-répertoires

45

Je suis imbriqué dans une arborescence de fichiers et j'aimerais trouver quel répertoire parent contient un fichier.

Par exemple, je suis dans un ensemble de référentiels Git imbriqués et je veux trouver le répertoire .git contrôlant les fichiers où je suis actuellement. J'espérerais quelque chose comme find -searchup -iname ".git"

Vincent Scheib
la source

Réponses:

16

Une version encore plus générale qui permet d’utiliser des findoptions:

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

Par exemple (en supposant que le script soit enregistré sous find_up.sh)

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

... affichera les noms de tous some_dirles ancêtres de (avec lui-même) jusqu'à /ce qu'un fichier contenant le motif se trouve.

Lors de l'utilisation readlink -fdu script ci-dessus, les liens symboliques suivront à la hausse, comme indiqué dans les commentaires. Vous pouvez utiliser à la realpath -splace, si vous voulez suivre les chemins par nom ("/ foo / bar" ira jusqu'à "foo" même si "bar" est un lien symbolique) - cependant cela nécessite une installation realpathqui n'est pas installée par défaut sur la plupart des plates-formes.

sinelaw
la source
le problème avec ceux-ci est la façon dont il résout les liens. Par exemple, si je suis dans ~ / foo / bar et que bar est un lien symbolique vers / some / other / place, alors ~ / foo et ~ / ne seront jamais recherchés.
Erik Aronesty
@ErikAronesty, vous avez raison - a mis à jour la réponse. Vous pouvez utiliser realpath -spour obtenir le comportement que vous souhaitez.
Sinelaw
cela fonctionne lorsque vous spécifiez un chemin d'accès complet à la recherche. Malheureusement, sur centos 5.8, realpath -s <dir> a un bug où si <dir> est relatif, il résout quand même les liens symboliques ... malgré sa documentation.
Erik Aronesty
readlinkne fait pas partie de la norme. Un script portable pourrait être mis en œuvre avec uniquement les fonctionnalités du shell POSIX.
Schily
1
Pour votre information, cela ne fonctionne pas dans zsh car il redéfinit $ path afin que le shell ne puisse pas trouver findou readlink. Je suggère de le changer pour quelque chose comme à la $curpathplace afin de ne pas masquer la variable shell.
Skyler
28
git rev-parse --show-toplevel

affichera le répertoire de niveau supérieur du référentiel actuel, si vous en êtes.

Autres options connexes:

# `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
éphémère
la source
4
Cela ne fonctionne que pour git lui-même. La question est plus générique.
alex
1
Cela ne marchera pas,
Jason Pyeron
19

Une version généralisée de la réponse de Gilles, premier paramètre utilisé pour trouver la correspondance:

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

Maintient l'utilisation de sym-links.

Vincent Scheib
la source
15

Trouver ne peut pas le faire. Je ne peux penser à rien de plus simple qu'une boucle de shell. (Non testé, suppose qu'il n'y en a pas /.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

Dans le cas spécifique d'un référentiel git, vous pouvez laisser git faire le travail à votre place.

git_root=$(GIT_EDITOR=echo git config -e)
git_root=${git_root%/*}
Gilles, arrête de faire le mal
la source
1
Cool, cela me met sur la voie d'une solution générale - que je poste ici comme autre réponse.
Vincent Scheib
12

Si vous utilisez zsh avec la fonctionnalité étendue de globbing activée, vous pouvez le faire avec 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'

Explication (citée de man zshexpn):

Globbing récursif

Un composant chemin de la forme (foo/)#correspond à un chemin composé de zéro ou plusieurs répertoires correspondant au motif foo. En abrégé, cela **/équivaut à (*/)#.

Modificateurs

Après la désignation de mot facultative, vous pouvez ajouter une séquence d'un ou plusieurs des modificateurs suivants, chacun précédé d'un ':'. Ces modificateurs fonctionnent également sur le résultat de la génération du nom de fichier et du développement des paramètres, sauf indication contraire.

  • une
    • Convertissez un nom de fichier en chemin absolu: ajoute le cas échéant le répertoire en cours et résout toute utilisation de '..' et '.'
  • UNE
    • En tant que " a ", mais résolvez également l'utilisation de liens symboliques dans la mesure du possible. Notez que la résolution de '..' se produit avant la résolution des liens symboliques. Cet appel est équivalent à sauf si votre système a l' realpathappel système (les systèmes modernes le font).
  • h
    • Supprime un composant de chemin suivant, en laissant la tête. Cela fonctionne comme ' dirname'.

Crédits: Fauxsur #zshpour la suggestion initiale d'utilisation (../)#.git(:h).

impensé
la source
C’est incroyable… Si je pouvais seulement comprendre (indice: vous devriez me dire ) que (../)fait-on… Oh, et qui / quoi Faux on #zsh?
alex grey
1
@alexgray (../)seul ne veut pas dire grand-chose, mais (../)#signifie qu'il faut essayer de développer le motif à l'intérieur de la parenthèse au moins 0 fois et d'utiliser uniquement ceux qui existent réellement sur le système de fichiers. Etant donné que nous étendons de 0 à n répertoires parents, nous recherchons la racine du système de fichiers (remarque: vous voudrez probablement ajouter quelque chose après le motif pour le rendre significatif, mais pour l’illumination, exécutez print -l (../)#). Et #zshest un canal IRC, Fauxest un nom d'utilisateur sur celui-ci. Fauxsuggéré la solution, donc le crédit.
impensé
1
Cela les élargira tous. Vous voudrez peut-être: (../)#.git(/Y1:a:h)vous arrêter au premier trouvé (avec une version récente de zsh)
Stéphane Chazelas
1
Très utile, merci. Pour permettre aux globbing étendus de fairesetopt extended_glob
Shlomi
0

Cette version de findup prend en charge la syntaxe "find", comme la réponse de @ sinelaw, mais prend également en charge les liens symboliques sans avoir besoin de realpath. Il supporte également une fonction optionnelle "stop at", donc ça marche: findup .:~ -name foo... cherche foo sans passer par le répertoire home.

#!/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
la source
0

J'ai constaté que travailler avec des liens symboliques éliminait certaines des autres options. Surtout les réponses spécifiques Git. J'ai créé à mi-chemin mon propre favori à partir de cette réponse originale et plutôt efficace.

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

J'utilise des liens symboliques pour mes projets go, car go veut du code source à un emplacement donné et j'aime garder mes projets sous ~ / projects. Je crée le projet dans $ GOPATH / src et les lie symboliquement à ~ / projects. Ainsi, exécuter git rev-parse --show-toplevelimprime le répertoire $ GOPATH, pas le répertoire ~ / projects. Cette solution résout ce problème.

Je réalise que la situation est très spécifique, mais je pense que la solution est précieuse.

blockloop
la source
0

La solution de Vincent Scheib ne fonctionne pas pour les fichiers qui se trouvent dans le répertoire racine.

La version suivante le fait, et vous permet également de passer le répertoire de départ comme premier argument.

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

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

    [ -e "$path"/"$2" ] && echo "$path"/"$2"
}
Fabio A.
la source
0

J'ai modifié la solution de Sinelaw pour qu'elle soit POSIX. Il ne suit pas les liens symboliques et inclut la recherche dans le répertoire racine en utilisant une boucle 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
la source