найти поиск в родительских каталогах вместо подкаталогов

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которая по умолчанию не установлена ​​в большинство платформ.

sinelaw
источник
проблема в том, как он разрешает ссылки. например, если я нахожусь в ~ / foo / bar, а bar является символической ссылкой на / some / other / place, то ~ / foo и ~ / никогда не будут найдены.
Эрик Аронесты
@ErikAronesty, ты прав - обновил ответ. Вы можете использовать, realpath -sчтобы получить поведение, которое вы хотите.
Синелав
это работает, когда вы указываете полный путь для поиска. к сожалению, в centos 5.8 в realpath -s <dir> есть ошибка, при которой если <dir> относительный, то он все равно разрешает символические ссылки ... несмотря на документацию.
Эрик Аронесты
readlinkне является частью стандарта. Переносимый скрипт может быть реализован только с функциями оболочки POSIX.
щили
1
Кстати, это не работает в zsh, потому что переопределяет $ path, поэтому оболочка не может найти findили readlink. $curpathВместо этого я предлагаю изменить его на что-то подобное, чтобы оно не скрывало переменную оболочки.
Скайлер
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
ephemient
источник
4
Это работает только для самого Git. Вопрос более общий.
Алекс
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):

Рекурсивное смазывание

Компонент pathname формы (foo/)#соответствует пути, состоящему из нуля или более каталогов, соответствующих шаблону foo. Как сокращение, **/эквивалентно (*/)#.

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

После необязательного обозначения слова вы можете добавить последовательность из одного или нескольких из следующих модификаторов, каждому из которых предшествует знак «:». Эти модификаторы также работают с результатом генерации имени файла и расширения параметров, если не указано иное.

  • a
    • Превратите имя файла в абсолютный путь: при необходимости добавляет текущий каталог в каталог и разрешает любое использование «..» и «.»
  • A
    • Как « а », но также разрешить использование символических ссылок, где это возможно. Обратите внимание, что разрешение '..' происходит до разрешения символических ссылок. Этот вызов эквивалентен, если ваша система не имеет realpathсистемного вызова (современные системы делают).
  • час
    • Удалите конечный компонент имени пути, оставив заголовок. Это работает как ' dirname'.

Кредиты: Fauxна #zshдля первоначального предложения использования (../)#.git(:h).

неосмысленное
источник
Это удивительно ... Если бы я только мог сообразить (подсказка: вы должны сказать мне ), что (../)делает ... О, и кто / что Faux on #zsh?
Алекс
1
@alexgray сам по (../)себе не значит много, но (../)#означает попытаться развернуть шаблон внутри круглых скобок 0 или более раз и использовать только те, которые действительно существуют в файловой системе. Поскольку мы расширяем от 0 до n родительских каталогов, мы эффективно ищем вверх в корне файловой системы (примечание: вы, вероятно, хотите добавить что-то после шаблона, чтобы сделать его осмысленным, но для выполнения просветления print -l (../)#). И #zshэто IRC канал, Fauxэто имя пользователя на нем. Fauxпредложил решение, поэтому кредит.
размышлений
1
Это расширит их все. Вы можете захотеть: (../)#.git(/Y1:a:h)остановиться на первом найденном (с последней версией zsh)
Стефан Шазелас
1
Очень полезно, спасибо. Чтобы включить расширенную глобализацию, сделайтеsetopt extended_glob
Шломи,
0

Эта версия findup поддерживает синтаксис "find", как и ответ @ sinelaw, но также поддерживает символические ссылки без необходимости использования realpath. Он также поддерживает опциональную функцию "stop at", поэтому она работает: findup .:~ -name foo... ищет foo, не передавая домашний каталог.

#!/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, потому что go хочет, чтобы исходный код находился в определенном месте, и мне нравится хранить свои проекты в ~ / projects. Я создаю проект в $ GOPATH / src и символическую ссылку на ~ / projects. Таким образом, запуск git rev-parse --show-toplevelраспечатывает каталог $ GOPATH, а не каталог ~ / projects. Это решение решает эту проблему.

Я понимаю, что это очень специфическая ситуация, но я думаю, что решение является ценным.

blockloop
источник
0

Решение Винсента Шейба не работает для файлов, которые находятся в корневом каталоге.

Следующая версия делает, а также позволяет передать начальный каталог в качестве 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
dosentmatter
источник