знайти пошук у батьківських каталогах замість підкаталогів


45

Я вкладений глибоко в дерево файлів, і я хотів би знайти, який батьківський каталог містить файл.

Наприклад, я перебуваю в наборі вкладених сховищ Git і хочу знайти каталог .git, що контролює файли, в яких я зараз перебуваю. Я сподіваюся на щось на кшталт find -searchup -iname ".git"

Відповіді:


16

Ще більш загальна версія, яка дозволяє використовувати findпараметри:

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

Наприклад (якщо припустимо, що сценарій збережено як find_up.sh)

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

... надрукує імена всіх some_dirпредків Росії (включаючи себе), до /яких знайдеться файл із шаблоном.

При використанні readlink -fвищевказаного сценарію будуть слідувати посилання на шляху вгору, як зазначено в коментарях. Ви можете використовувати realpath -sнатомість, якщо ви хочете слідувати шляхами вгору по імені ("/ foo / bar" підніметься до "foo", навіть якщо "bar" є символьним посиланням) - однак для цього потрібна установка, realpathяка не встановлена ​​за замовчуванням у більшість платформ.


проблема з цим полягає в тому, як вони вирішують посилання. наприклад, якщо я в ~ / foo / bar, а bar є символьним посиланням на / some / other / place, то ~ / foo і ~ / ніколи не будуть шукатись.
Ерік Аронесті

@ErikAronesty, ти маєш рацію - оновив відповідь. Ви можете використовувати realpath -sдля отримання потрібної вам поведінки.
sinelaw

що працює, коли ви вказуєте повний шлях до пошуку. на жаль, на Centos 5.8, realpath -s <dir> має помилку, де якщо <dir> відносний, то він вирішує символьні посилання все одно ... незважаючи на документацію.
Ерік Аронесті

readlinkне є частиною стандарту. Портативний скрипт може бути реалізований лише з функціями оболонки POSIX.
schily

1
FYI, це не працює в Zsh , тому що він перевизначає $ шлях , щоб оболонка не може знайти findабо readlink. Я пропоную змінити його на щось подібне, $curpathщоб він не затіняв змінну оболонки.
Skyler

28
git rev-parse --show-toplevel

буде надрукувати каталог верхнього рівня поточного сховища, якщо ви знаходитесь в одному.

Інші пов'язані варіанти:

# `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
Це працює лише для самого git. Питання більш загальне.
alex

1
Це не вийде в голому репо,
Джейсон П'єрон,

19

Узагальнена версія відповіді Жиля, перший параметр, який використовується для пошуку відповідності:

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

Тримає використання символьних посилань.


15

Знайти не може цього зробити. Я не можу придумати нічого простішого, ніж петлю оболонки. (Неперевірений, припускає, що його немає /.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

У конкретному випадку сховища git ви можете дозволити git виконувати роботу за вас.

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

1
Круто, це ставить мене на шлях загального рішення - яке я публікую тут як ще одну відповідь.
Вінсент Шейб

12

Якщо ви використовуєте zsh з увімкненим розширеним глобусом, ви можете це зробити за допомогою 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'

Пояснення (цитується з man zshexpn):

Рекурсивне поглинання

Компонент форми назви (foo/)#шляху відповідає шляху, що складається з нуля або більше каталогів, що відповідають шаблону foo. Як скорочення, **/це рівнозначно (*/)#.

Модифікатори

Після необов'язкового позначення слів ви можете додати послідовність одного або декількох наступних модифікаторів, кожному з яких передує ':'. Ці модифікатори також працюють над результатом генерації імені файлів та розширенням параметрів, за винятком випадків, коли зазначено.

  • а
    • Перетворіть ім'я файлу в абсолютний шлях: при необхідності попередньо додає поточний каталог і вирішує будь-яке використання ".." і "."
  • А
    • Як " а ", але також дозволяйте використовувати символьні посилання, де це можливо. Зауважте, що роздільна здатність ".." виникає до вирішення символьних посилань. Цей виклик еквівалентний випадкам, якщо ваша система не має realpathсистемного виклику (це роблять сучасні системи).
  • год
    • Видаліть компонент, що містить контур, залишаючи голову. Це працює як " dirname".

Кредити: Fauxувімкнено #zshдля початкової пропозиції щодо використання (../)#.git(:h).


Це дивовижно ... Якби я міг лише зрозуміти (натяк: ти мусиш мені сказати ), що (../)робиш ... О, і хто / що таке Faux on #zsh?
alex grey

1
(../)Сам @alexgray не означає багато, але (../)#означає спробувати розширити шаблон всередині дужок 0 і більше разів і використовувати лише ті, які є фактично у файловій системі. Оскільки ми розширюємо від 0 до n батьківських каталогів, ми ефективно шукаємо вгору до кореня файлової системи (зверніть увагу: ви, ймовірно, хочете додати щось після шаблону, щоб зробити його значущим, але для виконання просвітлення print -l (../)#). І #zshце канал IRC, Fauxце ім'я користувача. Fauxзапропонував рішення, тому кредит.
нероздуманий

1
Це розширить їх усіх. Можливо, ви захочете: (../)#.git(/Y1:a:h)зупинитися на першому знайденому (із нещодавньою версією zsh)
Stéphane Chazelas

1
Дуже корисно, дякую. Щоб увімкнути розширений глобус, робітьsetopt extended_glob
Shlomi

0

Ця версія findup підтримує синтаксис "find", як і відповідь @ sinelaw, але також підтримує посилання, не потребуючи realpath. Він також підтримує додаткову функцію "зупинитись на", тому це працює: findup .:~ -name foo... шукає foo, не передаючи домашній dir.

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

Я виявив, що робота з символьними посиланнями вбиває деякі інші варіанти. Особливо відповіді на git конкретні. Я на півдорозі створив свого власного улюбленого з цієї оригінальної відповіді, яка є досить ефективною.

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

Я використовую посилання для своїх проектів, тому що go хоче вихідний код у певному місці, і мені подобається тримати свої проекти під ~ / проектами. Я створюю проект у $ GOPATH / src і посилаю їх на ~ / проекти. Таким чином, при запуску git rev-parse --show-toplevelвиводиться каталог $ GOPATH, а не каталог ~ / projects. Це рішення вирішує цю проблему.

Я усвідомлюю, що це дуже специфічна ситуація, але я вважаю, що рішення є цінним.


0

Вінсент Шейбі рішення не працює для файлів , які знаходяться в кореневому каталозі.

Наступна версія робить, а також дозволяє передати початковий dir як 1-й аргумент.

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

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

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

0

Я змінив рішення sinelaw на POSIX. Він не дотримується символьних посилань і включає пошук кореневого каталогу за допомогою циклу 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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.