Suche in übergeordneten Verzeichnissen statt in Unterverzeichnissen

45

Ich bin tief in einem Dateibaum verschachtelt und möchte herausfinden, welches übergeordnete Verzeichnis eine Datei enthält.

ZB bin ich in einer Reihe von verschachtelten Git-Repositories und möchte das .git-Verzeichnis finden, das die Dateien steuert, in denen ich mich gerade befinde. Ich hoffe auf so etwas wie find -searchup -iname ".git"

Vincent Scheib
quelle

Antworten:

16

Eine noch allgemeinere Version, die die Verwendung von findOptionen ermöglicht:

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

Zum Beispiel (vorausgesetzt, das Skript wird gespeichert als find_up.sh)

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

... gibt die Namen aller some_dirVorfahren (einschließlich sich selbst) aus, /in denen sich eine Datei mit dem Muster befindet.

Wenn Sie readlink -fdas obige Skript verwenden, folgen Sie den Symlinks auf dem Weg nach oben, wie in den Kommentaren angegeben. Sie können realpath -sstattdessen verwenden, wenn Sie Pfade nach Namen verfolgen möchten ("/ foo / bar" wird zu "foo", auch wenn "bar" ein Symlink ist) - dies erfordert jedoch die Installation, realpathdie nicht standardmäßig auf installiert ist die meisten Plattformen.

sinelaw
quelle
Das Problem dabei ist die Art und Weise, wie Links aufgelöst werden. Wenn ich zum Beispiel in ~ / foo / bar bin und bar ein Symlink zu / some / other / place ist, werden ~ / foo und ~ / niemals durchsucht.
Erik Aronesty
@ErikAronesty, Sie haben Recht - die Antwort wurde aktualisiert. Mit können realpath -sSie das gewünschte Verhalten erzielen.
sinelaw
Dies funktioniert, wenn Sie einen vollständigen Pfad für die Suche angeben. Leider hat Realpath -s <dir> auf Centos 5.8 einen Fehler, bei dem, wenn <dir> relativ ist, Symlinks trotzdem aufgelöst werden ... trotz Dokumentation.
Erik Aronesty
readlinkist nicht Teil des Standards. Ein portierbares Skript kann nur mit POSIX-Shell-Funktionen implementiert werden.
Schily
1
Zu Ihrer Information, dies funktioniert in zsh nicht, da es $ path neu definiert, so dass die Shell findor nicht finden kann readlink. Ich schlage vor, es in so etwas wie zu ändern, $curpathdamit es die Shell-Variable nicht beschattet.
Skyler
28
git rev-parse --show-toplevel

Gibt das oberste Verzeichnis des aktuellen Repository aus, sofern Sie sich in einem befinden.

Weitere verwandte Optionen:

# `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
vergänglich
quelle
4
Dies funktioniert nur für Git selbst. Die Frage ist allgemeiner.
Alex
1
Dies wird nicht in einem nackten Repo funktionieren,
Jason Pyeron
19

Eine verallgemeinerte Version von Gilles 'Antwort, dem ersten Parameter, der zur Suche nach Übereinstimmungen verwendet wird:

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

Hält die Verwendung von Sym-Links.

Vincent Scheib
quelle
15

Find kann das nicht. Ich kann mir nichts Einfacheres vorstellen als eine Shell-Schleife. (Ungetestet, vorausgesetzt, es gibt keine /.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

Für den speziellen Fall eines Git-Repositorys können Sie Git die Arbeit für Sie erledigen lassen.

git_root=$(GIT_EDITOR=echo git config -e)
git_root=${git_root%/*}
Gilles 'SO - hör auf böse zu sein'
quelle
1
Cool, das bringt mich auf den Weg zu einer allgemeinen Lösung - die ich hier als weitere Antwort poste.
Vincent Scheib
12

Wenn Sie zsh mit aktiviertem Extended Globbing verwenden, können Sie dies mit einem Oneliner tun:

(../)#.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'

Erklärung (zitiert von man zshexpn):

Rekursives Globbing

Eine Pfadname-Komponente des Formulars (foo/)#stimmt mit einem Pfad überein, der aus null oder mehr Verzeichnissen besteht, die mit dem Muster foo übereinstimmen. Als Kurzform **/entspricht (*/)#.

Modifikatoren

Nach dem optionalen Wortbezeichner können Sie eine Folge von einem oder mehreren der folgenden Modifikatoren einfügen, denen jeweils ein ':' vorangestellt ist. Diese Modifikatoren wirken sich auch auf das Ergebnis der Dateinamengenerierung und Parametererweiterung aus, sofern nicht anders angegeben.

  • ein
    • Verwandle einen Dateinamen in einen absoluten Pfad: Stellt bei Bedarf das aktuelle Verzeichnis voran und löst jegliche Verwendung von '..' und '.'
  • EIN
    • Als ' ein ', aber auch die Verwendung von symbolischen Links nach Möglichkeit auflösen. Beachten Sie, dass die Auflösung von '..' vor der Auflösung von symbolischen Links erfolgt. Dieser Aufruf entspricht a, es sei denn, Ihr System verfügt über den realpathSystemaufruf (moderne Systeme tun dies).
  • h
    • Entfernen Sie eine nachgestellte Pfadnamenskomponente und lassen Sie den Kopf frei. Das funktioniert wie ' dirname'.

Credits: Fauxein #zshfür den ersten Vorschlag der Verwendung (../)#.git(:h).

ungedacht
quelle
Das ist erstaunlich ... Wenn ich nur herausfinden könnte (Hinweis: Du solltest es mir sagen ), was (../)passiert ... Oh, und wer / was ist das Faux on #zsh?
Alex Gray
1
@alexgray (../)allein bedeutet nicht viel, sondern (../)#bedeutet, dass Sie versuchen, das Muster in der Klammer 0 oder öfter zu erweitern und nur diejenigen zu verwenden, die tatsächlich im Dateisystem vorhanden sind. Da wir von 0 auf n übergeordnete Verzeichnisse expandieren, suchen wir effektiv bis zum Stammverzeichnis des Dateisystems (Anmerkung: Sie möchten wahrscheinlich etwas nach dem Muster hinzufügen, um es aussagekräftig zu machen, aber um die Aufklärung auszuführen print -l (../)#). Und #zshist ein IRC-Kanal, Fauxist ein Benutzername drauf. Fauxschlug die Lösung vor, daher der Kredit.
gedacht
1
Das wird sie alle erweitern. Vielleicht möchten Sie: (../)#.git(/Y1:a:h)bei der ersten gefundenen (mit einer neueren Version von zsh) anhalten
Stéphane Chazelas
1
Sehr hilfreich, danke. Um ein erweitertes Globbing zu ermöglichen, machen Siesetopt extended_glob
Shlomi
0

Diese Version von findup unterstützt die "find" -Syntax wie die Antwort von @ sinelaw, unterstützt jedoch auch Symlinks, ohne dass ein realer Pfad erforderlich ist. Es unterstützt auch eine optionale "Stopp bei" -Funktion, so dass dies funktioniert: findup .:~ -name foo... sucht nach foo, ohne das Heimatverzeichnis zu übergeben.

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

Ich habe festgestellt, dass die Arbeit mit Symlinks einige der anderen Optionen zunichte macht. Besonders die Git-spezifischen Antworten. Ich habe auf halbem Weg meinen eigenen Favoriten aus dieser originellen Antwort erstellt, die ziemlich effizient ist.

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

Ich verwende Symlinks für meine go-Projekte, weil go Quellcode an einem bestimmten Ort haben möchte und ich meine Projekte gerne unter ~ / projects behalten möchte. Ich erstelle das Projekt in $ GOPATH / src und verknüpfe sie mit ~ / projects. So läuft git rev-parse --show-topleveldruckt das $ GOPATH Verzeichnis, nicht das ~ / Projekte Verzeichnis. Diese Lösung löst dieses Problem.

Mir ist klar, dass dies eine sehr spezifische Situation ist, aber ich denke, dass die Lösung wertvoll ist.

Blockschleife
quelle
0

Die Lösung von Vincent Scheib funktioniert nicht für Dateien, die sich im Stammverzeichnis befinden.

In der folgenden Version können Sie auch das Startverzeichnis als erstes Argument übergeben.

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

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

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

Ich habe die Lösung von sinelaw in POSIX geändert . Es folgt keinen Symlinks und durchsucht das Stammverzeichnis mithilfe einer do while-Schleife.

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
quelle