trova la ricerca nelle directory principali anziché nelle sottodirectory


45

Sono annidato in profondità in un albero di file e vorrei trovare quale directory principale contiene un file.

Ad esempio, sono in un set di repository Git nidificati e voglio trovare la directory .git che controlla i file in cui mi trovo attualmente. Spero in qualcosa come find -searchup -iname ".git"

Risposte:


16

Una versione ancora più generale che consente di utilizzare le findopzioni:

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

Ad esempio (supponendo che lo script sia salvato come find_up.sh)

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

... stamperà i nomi di tutti some_dirgli antenati (incluso se stesso) fino a quando /viene trovato un file con il modello.

Quando si utilizza readlink -flo script sopra, seguiranno i collegamenti simbolici durante la salita, come indicato nei commenti. Puoi realpath -sinvece usare , se vuoi seguire i percorsi per nome ("/ foo / bar" passerà a "foo" anche se "bar" è un collegamento simbolico) - tuttavia ciò richiede l'installazione realpathche non è installata di default su la maggior parte delle piattaforme.


il problema con questi è il modo in cui risolve i collegamenti. per esempio se mi trovo in ~ / foo / bar e bar è un collegamento simbolico a / some / other / place, allora ~ / foo e ~ / non verranno mai cercati.
Erik Aronesty,

@ErikAronesty, hai ragione - aggiornata la risposta. È possibile utilizzare realpath -sper ottenere il comportamento desiderato.
sinelaw,

che funziona quando si specifica un percorso completo per la ricerca. purtroppo, su centos 5.8, realpath -s <dir> ha un bug in cui se <dir> è relativo, risolve comunque i collegamenti simbolici ... nonostante sia documentazione.
Erik Aronesty,

readlinknon fa parte dello standard. Uno script portatile potrebbe essere implementato con solo funzionalità shell POSIX.
schily

1
Cordiali saluti, questo non funziona in zsh perché ridefinisce $ path in modo che la shell non riesca a trovare findo readlink. Suggerisco di cambiarlo in qualcosa del genere, in $curpathmodo che non oscuri la variabile shell.
Skyler,

28
git rev-parse --show-toplevel

stamperà la directory di livello superiore del repository corrente, se ci si trova in uno.

Altre opzioni correlate:

# `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

4
Questo funziona solo per Git stesso. La domanda è più generica.
alex

1
Questo non funzionerà in un repository nudo,
Jason Pyeron,

19

Una versione generalizzata della risposta di Gilles, primo parametro utilizzato per trovare la corrispondenza:

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

Mantiene l'uso di sym-link.


15

Trova non può farlo. Non riesco a pensare a qualcosa di più semplice di un loop shell. (Non testato, presuppone che non ci sia /.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

Per il caso specifico di un repository git, puoi lasciare che git faccia il lavoro per te.

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

1
Bene, questo mi mette su una strada per una soluzione generale - che inserisco come un'altra risposta qui.
Vincent Scheib,

12

Se stai usando zsh con il globbing esteso abilitato, puoi farlo 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'

Spiegazione (citato da man zshexpn):

Globbing ricorsivo

Un componente pathname del modulo (foo/)#corrisponde a un percorso costituito da zero o più directory corrispondenti al modello foo. Come stenografia, **/equivale a (*/)#.

modificatori

Dopo il designatore di parole opzionale, è possibile aggiungere una sequenza di uno o più dei seguenti modificatori, ciascuno preceduto da un ':'. Questi modificatori funzionano anche sul risultato della generazione del nome file e dell'espansione dei parametri, tranne dove indicato.

  • un'
    • Trasforma il nome di un file in un percorso assoluto: antepone la directory corrente, se necessario, e risolve qualsiasi uso di '..' e '.'
  • UN
    • Come ' a ', ma risolve anche l'uso di collegamenti simbolici ove possibile. Si noti che la risoluzione di ".." si verifica prima della risoluzione dei collegamenti simbolici. Questa chiamata equivale a a meno che il tuo sistema non abbia la realpathchiamata di sistema (i sistemi moderni hanno).
  • h
    • Rimuovere un componente di percorso finale, lasciando la testa. Funziona come " dirname".

Credits: Fauxsu #zshper il suggerimento iniziale di utilizzo (../)#.git(:h).


Questo è fantastico ... Se solo potessi capire (suggerimento: dovresti dirmi ) cosa (../)sta facendo ... Oh, e chi / che cosa è Faux on #zsh?
alex gray,

1
@alexgray da (../)solo non significa molto, ma (../)#significa provare a espandere il pattern all'interno della parentesi 0 o più volte, e usare solo quelli effettivamente esistenti sul filesystem. Poiché stiamo espandendo da 0 a n directory madri, cerchiamo efficacemente verso l'alto fino alla radice del file system (nota: probabilmente vorrai aggiungere qualcosa dopo il modello per renderlo significativo, ma eseguire l'illuminazione print -l (../)#). Ed #zshè un canale IRC, Fauxè un nome utente su di esso. Fauxha suggerito la soluzione, quindi il merito.
impensato il

1
Questo li espanderà tutti. Potresti voler: (../)#.git(/Y1:a:h)fermarti al primo trovato (con una versione recente di zsh)
Stéphane Chazelas,

1
Molto utile, grazie. Per abilitare il globbing esteso, faisetopt extended_glob
Shlomi,

0

Questa versione di findup supporta la sintassi "find", come la risposta di @ sinelaw, ma supporta anche i link simbolici senza bisogno di realpath. Supporta anche una funzione opzionale "stop at", quindi funziona: findup .:~ -name foo... cerca foo senza passare la home directory.

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

Ho scoperto che lavorare con i collegamenti simbolici uccide alcune delle altre opzioni. Soprattutto le risposte specifiche git. A metà strada ho creato il mio preferito da questa risposta originale che è abbastanza 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"
}

Sto usando i collegamenti simbolici per i miei progetti go perché go vuole il codice sorgente in una determinata posizione e mi piace mantenere i miei progetti in ~ / progetti. Creo il progetto in $ GOPATH / src e li collego a ~ / progetti. Quindi l'esecuzione git rev-parse --show-toplevelstampa la directory $ GOPATH, non la directory ~ / projects. Questa soluzione risolve questo problema.

Mi rendo conto che questa è una situazione molto specifica, ma penso che la soluzione sia preziosa.


0

La soluzione di Vincent Scheib non funziona per i file che risiedono nella directory principale.

La versione seguente lo fa e ti consente anche di passare la directory iniziale come primo argomento.

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

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

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

0

Ho modificato la soluzione di sinelaw in POSIX. Non segue i collegamenti simbolici e include la ricerca nella directory principale usando un ciclo 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
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.