temukan pencarian di direktori induk daripada subdirektori

45

Saya bersarang jauh di dalam pohon file, dan saya ingin menemukan direktori induk mana yang berisi file.

Misalnya saya dalam satu set repositori Git yang bersarang dan ingin menemukan direktori .git yang mengendalikan file yang saat ini saya gunakan. Saya berharap untuk sesuatu seperti find -searchup -iname ".git"

Vincent Scheib
sumber

Jawaban:

16

Versi yang lebih umum yang memungkinkan menggunakan findopsi:

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

Misalnya (dengan asumsi skrip disimpan sebagai find_up.sh)

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

... akan mencetak nama-nama semua some_dirleluhur (termasuk dirinya sendiri) hingga /di mana file dengan pola ditemukan.

Saat menggunakan readlink -fskrip di atas akan mengikuti symlinks di jalan, seperti yang tercantum dalam komentar. Anda dapat menggunakan realpath -ssebagai gantinya, jika Anda ingin mengikuti jalur berdasarkan nama ("/ foo / bar" akan naik ke "foo" bahkan jika "bar" adalah symlink) - namun itu membutuhkan pemasangan realpathyang tidak diinstal secara default pada kebanyakan platform.

sinelaw
sumber
masalah dengan ini adalah cara menyelesaikan tautan. misalnya jika saya di ~ / foo / bar, dan bar adalah symlink ke / some / other / place, maka ~ / foo dan ~ / tidak akan pernah dicari.
Erik Aronesty
@ErikAronesty, Anda benar - memperbarui jawabannya. Anda dapat menggunakan realpath -suntuk mendapatkan perilaku yang Anda inginkan.
sinelaw
yang berfungsi saat Anda menentukan jalur lengkap untuk mencari. Sayangnya, pada centos 5.8, realpath -s <dir> memiliki bug di mana jika <dir> adalah relatif, maka itu tetap menyelesaikan symlinks ... terlepas dari dokumentasinya.
Erik Aronesty
readlinkbukan bagian dari standar. Script portabel dapat diimplementasikan dengan hanya fitur shell POSIX.
schily
1
FYI, ini tidak berfungsi di zsh karena mendefinisikan ulang $ path sehingga shell tidak dapat menemukan findatau readlink. Saya sarankan mengubahnya menjadi sesuatu seperti $curpathitu supaya tidak membayangi variabel shell.
Skyler
28
git rev-parse --show-toplevel

akan mencetak direktori tingkat atas dari repositori saat ini, jika Anda berada dalam satu.

Opsi terkait lainnya:

# `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
singkat
sumber
4
Ini hanya berfungsi untuk git itu sendiri. Pertanyaannya lebih umum.
alex
1
Ini tidak akan bekerja dalam repo kosong,
Jason Pyeron
19

Versi umum jawaban Gilles, parameter pertama yang digunakan untuk menemukan kecocokan:

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

Tetap menggunakan sym-link.

Vincent Scheib
sumber
15

Temukan tidak dapat melakukannya. Saya tidak bisa memikirkan sesuatu yang lebih sederhana dari pada shell loop. (Belum diuji, menganggap tidak ada /.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

Untuk kasus tertentu dari repositori git, Anda dapat membiarkan git melakukan pekerjaan untuk Anda.

git_root=$(GIT_EDITOR=echo git config -e)
git_root=${git_root%/*}
Gilles 'SANGAT berhenti menjadi jahat'
sumber
1
Keren, ini menempatkan saya pada jalur untuk solusi umum - yang saya posting sebagai jawaban lain di sini.
Vincent Scheib
12

Jika Anda menggunakan zsh dengan globbing diperpanjang diaktifkan, Anda bisa melakukannya dengan 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'

Penjelasan (dikutip dari man zshexpn):

Globbing rekursif

Komponen pathname dari formulir (foo/)#cocok dengan path yang terdiri dari nol atau lebih direktori yang cocok dengan pola foo. Sebagai steno, **/setara dengan (*/)#.

Pengubah

Setelah penunjuk kata opsional, Anda dapat menambahkan urutan satu atau lebih dari pengubah berikut, masing-masing didahului oleh ':'. Pengubah ini juga bekerja pada hasil pembuatan nama file dan perluasan parameter, kecuali jika disebutkan.

  • Sebuah
    • Ubah nama file menjadi jalur absolut: tambahkan dulu direktori saat ini, jika perlu, dan selesaikan segala penggunaan '..' dan '.'
  • SEBUAH
    • Sebagai ' a ', tetapi juga selesaikan penggunaan tautan simbolik jika memungkinkan. Perhatikan bahwa resolusi '..' terjadi sebelum resolusi tautan simbolik. Panggilan ini setara dengan kecuali sistem Anda memiliki realpathpanggilan sistem (sistem modern lakukan).
  • h
    • Hapus komponen trailing pathname, biarkan kepala. Ini berfungsi seperti ' dirname'.

Kredit: Fauxaktif #zshuntuk saran awal penggunaan (../)#.git(:h).

tidak terpikirkan
sumber
Ini luar biasa ... Jika saya hanya bisa mencari tahu (petunjuk: Anda harus memberi tahu saya ) apa (../)yang dilakukan ... Oh, dan siapa / apa Faux on #zsh?
alex grey
1
@alexgray (../)sendiri tidak berarti banyak, tetapi (../)#berarti mencoba untuk memperluas pola di dalam tanda kurung 0 atau lebih kali, dan hanya menggunakan yang benar-benar ada pada sistem file. Karena kami memperluas dari 0 ke direktori induk, kami secara efektif mencari ke atas ke akar sistem file (catatan: Anda mungkin ingin menambahkan sesuatu setelah pola untuk membuatnya bermakna, tetapi untuk pelaksanaan pencerahan print -l (../)#). Dan #zshmerupakan saluran IRC, Fauxadalah nama pengguna di dalamnya. Fauxmenyarankan solusi, oleh karena itu kredit.
terpikirkan
1
Itu akan memperluas semuanya. Anda mungkin ingin: (../)#.git(/Y1:a:h)berhenti di yang pertama ditemukan (dengan versi terbaru dari zsh)
Stéphane Chazelas
1
Terima kasih banyak. Untuk mengaktifkan globbing tambahan, lakukansetopt extended_glob
Shlomi
0

Versi findup ini mendukung sintaks "find", seperti jawaban @ sinelaw, tetapi juga mendukung symlink tanpa memerlukan realpath. Ini juga mendukung fitur "berhenti di" opsional, jadi ini berfungsi: findup .:~ -name foo... mencari foo tanpa melewati direktori 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
sumber
0

Saya telah menemukan bahwa bekerja dengan symlink membunuh beberapa opsi lain. Terutama jawaban spesifik git. Saya sudah setengah jalan membuat favorit saya sendiri dari jawaban asli ini yang cukup efisien.

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

Saya menggunakan symlinks untuk proyek go saya karena go ingin kode sumber di lokasi tertentu dan saya suka menyimpan proyek saya di bawah ~ / proyek. Saya membuat proyek dalam $ GOPATH / src dan menghubungkannya dengan ~ / proyek. Jadi menjalankan git rev-parse --show-toplevelmencetak direktori $ GOPATH, bukan direktori ~ / projects. Solusi ini memecahkan masalah itu.

Saya menyadari ini adalah situasi yang sangat spesifik, tetapi saya pikir solusinya sangat berharga.

blockloop
sumber
0

Solusi Vincent Scheib tidak berfungsi untuk file yang berada di direktori root.

Versi berikut tidak, dan juga memungkinkan Anda melewati dir mulai sebagai argumen 1.

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

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

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

Saya memodifikasi solusi sinelaw menjadi POSIX. Itu tidak mengikuti symlink dan termasuk mencari direktori root dengan menggunakan 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
sumber