znajdź wyszukiwanie w katalogach nadrzędnych zamiast w podkatalogach

45

Jestem zagnieżdżony głęboko w drzewie plików i chciałbym dowiedzieć się, który katalog nadrzędny zawiera plik.

Np. Jestem w zestawie zagnieżdżonych repozytoriów Git i chcę znaleźć katalog .git kontrolujący pliki, w których aktualnie jestem. Mam nadzieję na coś takiego jak find -searchup -iname ".git"

Vincent Scheib
źródło

Odpowiedzi:

16

Jeszcze bardziej ogólna wersja, która umożliwia korzystanie z findopcji:

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

Na przykład (zakładając, że skrypt zostanie zapisany jako find_up.sh)

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

... wypisze nazwy wszystkich some_dirprzodków (w tym siebie), aż do /znalezienia pliku ze wzorem.

Podczas korzystania readlink -fz powyższego skryptu w drodze w górę będą podążać dowiązania symboliczne, jak zauważono w komentarzach. Zamiast tego możesz użyć realpath -s, jeśli chcesz podążać ścieżkami według nazwy („/ foo / bar” przejdzie do „foo”, nawet jeśli „bar” jest dowiązaniem symbolicznym) - wymaga to jednak instalacji, realpathktóra nie jest domyślnie instalowana na większość platform.

sinelaw
źródło
problemem jest sposób, w jaki rozwiązuje linki. na przykład, jeśli jestem w ~ / foo / bar, a bar jest dowiązaniem symbolicznym do / some / other / place, to ~ / foo i ~ / nigdy nie będą przeszukiwane.
Erik Aronesty
@ErikAronesty, masz rację - zaktualizowałem odpowiedź. Możesz użyć, realpath -saby uzyskać pożądane zachowanie.
sinelaw
działa to po określeniu pełnej ścieżki wyszukiwania. niestety na centos 5.8 realpath -s <katalog> zawiera błąd, w którym jeśli <katalog> jest względny, to i tak rozwiązuje dowiązania symboliczne ... pomimo swojej dokumentacji.
Erik Aronesty,
readlinknie jest częścią standardu. Przenośny skrypt można zaimplementować tylko z funkcjami powłoki POSIX.
schily
1
Do twojej wiadomości, to nie działa w Zsh, ponieważ redefiniuje $ path, aby powłoka nie mogła znaleźć findlub readlink. Sugeruję zmianę go na coś podobnego $curpathzamiast tego, aby nie przesłaniała zmiennej powłoki.
Skyler
28
git rev-parse --show-toplevel

wydrukuje katalog najwyższego poziomu bieżącego repozytorium, jeśli w nim jesteś.

Inne powiązane opcje:

# `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
efemeryczny
źródło
4
Działa to tylko dla samego gita. Pytanie jest bardziej ogólne.
alex
1
To nie zadziała w nagim repo,
Jason Pyeron
19

Uogólniona wersja odpowiedzi Gillesa, pierwszy parametr użyty do znalezienia dopasowania:

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

Utrzymuje użycie łączy sym.

Vincent Scheib
źródło
15

Find nie może tego zrobić. Nie mogę wymyślić nic prostszego niż pętla powłoki. (Nieprzetestowane, zakłada, że ​​nie ma /.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

W konkretnym przypadku repozytorium git możesz pozwolić git wykonać za ciebie pracę.

git_root=$(GIT_EDITOR=echo git config -e)
git_root=${git_root%/*}
Gilles „SO- przestań być zły”
źródło
1
Fajnie, to stawia mnie na drodze do ogólnego rozwiązania - które zamieszczam jako kolejną odpowiedź tutaj.
Vincent Scheib
12

Jeśli używasz zsh z włączonym rozszerzonym globowaniem, możesz to zrobić za pomocą programu 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'

Objaśnienie (cytowane z man zshexpn):

Recursive Globbing

Składnik nazwy ścieżki w formularzu (foo/)#odpowiada ścieżce składającej się z zero lub więcej katalogów pasujących do wzorca foo. Jako skrót **/jest równoważne z (*/)#.

Modyfikatory

Po opcjonalnym desygnatorze słowa możesz dodać sekwencję jednego lub więcej następujących modyfikatorów, każdy poprzedzony znakiem „:”. Modyfikatory te działają również w wyniku generowania nazw plików i rozszerzania parametrów, chyba że zaznaczono inaczej.

  • za
    • Zamień nazwę pliku na ścieżkę bezwzględną: w razie potrzeby wstawia bieżący katalog i rozwiązuje każde użycie „..” i „.”
  • ZA
    • Jako „ a ”, ale w miarę możliwości rozwiązuj także użycie dowiązań symbolicznych. Zauważ, że rozdzielczość „..” występuje przed rozdzielczością dowiązań symbolicznych. To wywołanie jest równoważne, chyba że twój system ma realpathwywołanie systemowe (nowoczesne systemy to robią).
  • h
    • Usuń końcowy składnik nazwy ścieżki, pozostawiając głowę. To działa jak „ dirname”.

Kredyty: Fauxwłączone #zshdla początkowej sugestii użycia (../)#.git(:h).

nieprzemyślane
źródło
To niesamowite ... Gdybym tylko mógł dowiedzieć się (wskazówka: powinieneś mi powiedzieć ), co (../)robi ... Och, a kto / co jest Faux on #zsh?
Alex Grey
1
(../)Sam @alexgray nie znaczy wiele, ale (../)#oznacza próbę rozszerzenia wzorca w nawiasie 0 lub więcej razy i używaj tylko tych, które faktycznie istnieją w systemie plików. Ponieważ rozwijamy się od 0 do n katalogów nadrzędnych, skutecznie wyszukujemy w górę do katalogu głównego systemu plików (uwaga: prawdopodobnie chcesz dodać coś po wzorcu, aby było to znaczące, ale aby wykonać oświecenie print -l (../)#). I #zshjest kanałem IRC, Fauxjest na nim nazwą użytkownika. Fauxzasugerował rozwiązanie, dlatego kredyt.
nieprzemyślane
1
To rozwinie je wszystkie. Możesz chcieć: (../)#.git(/Y1:a:h)zatrzymać się przy pierwszym znalezionym (z najnowszą wersją zsh)
Stéphane Chazelas
1
Bardzo pomocny, dzięki. Aby włączyć rozszerzone globowanie, wykonajsetopt extended_glob
Shlomi
0

Ta wersja wyszukiwania obsługuje składnię „znajdź”, jak odpowiedź @ sinelaw, ale obsługuje także dowiązania symboliczne bez potrzeby podawania prawdziwej ścieżki. Obsługuje również opcjonalną funkcję „stop at”, więc działa to: findup .:~ -name foo... wyszukuje foo bez podawania katalogu domowego.

#!/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
źródło
0

Odkryłem, że praca z dowiązaniami symbolicznymi zabija niektóre inne opcje. Zwłaszcza odpowiedzi na konkretne pytania. W połowie drogi stworzyłem swoją ulubioną z tej oryginalnej odpowiedzi, która jest dość skuteczna.

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

Używam dowiązań symbolicznych do moich projektów go, ponieważ go chce kodu źródłowego w określonej lokalizacji i lubię trzymać moje projekty w ~ / projects. Tworzę projekt w $ GOPATH / src i dowiązuję symbolicznie do ~ / projects. Tak działa git rev-parse --show-toplevelwypisuje katalogu $ GOPATH, a nie w katalogu ~ / projektów. To rozwiązanie rozwiązuje ten problem.

Zdaję sobie sprawę, że jest to bardzo specyficzna sytuacja, ale myślę, że to rozwiązanie jest cenne.

Blockloop
źródło
0

Rozwiązanie Vincenta Scheiba nie działa w przypadku plików znajdujących się w katalogu głównym.

Poniższa wersja zawiera, a także pozwala przekazać katalog początkowy jako pierwszy 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.
źródło
0

Zmodyfikowałem rozwiązanie sinelaw na POSIX. Nie podąża za dowiązaniami symbolicznymi i obejmuje przeszukiwanie katalogu głównego za pomocą pętli 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
źródło