サブディレクトリではなく親ディレクトリで検索を見つける


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」は「bar」がシンボリックリンクであっても「foo」になります)-ただし、realpathデフォルトではインストールされないインストールが必要ですほとんどのプラットフォーム。


これらの問題は、リンクを解決する方法です。たとえば、私が〜/ foo / barにいて、barが/ some / other / placeへのシンボリックリンクである場合、〜/ fooと〜/は検索されません。
エリックアロネスティ

@ErikAronesty、あなたは正しいです-答えを更新しました。を使用realpath -sして、必要な動作を取得できます。
シネロー

検索するフルパスを指定すると機能します。悲しいことに、centos 5.8では、realpath -s <dir>にはバグがあり、<dir>が相対である場合、ドキュメントにかかわらずシンボリックリンクを解決します...
エリックアロネスティ

readlink標準の一部ではありません。移植可能なスクリプトは、POSIXシェル機能のみで実装できます。
18年

1
参考までに、これは$ shを再定義してシェルがfindまたはを見つけられないため、zshでは機能しません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

4
これはgit自体でのみ機能します。質問はより一般的です。
アレックス

1
これは、裸のレポでは動作しません
ジェイソンPyeronに

19

Gillesの回答の一般化バージョン、最初のパラメーターは一致を見つけるために使用されます。

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に一致する0個以上のディレクトリで構成されるパスに一致します。短縮形として、**/と同等(*/)#です。

修飾子

オプションの単語指定子の後に、次の1つ以上の修飾子のシーケンスを追加できます。各修飾子の前には「:」が付きます。これらの修飾子は、特に明記されている場合を除き、ファイル名の生成とパラメーターの展開の結果にも機能します。

  • a
    • ファイル名を絶対パスに変換します。必要に応じて現在のディレクトリを追加し、「..」と「。」の使用を解決します。
  • A
    • ' a 'ですが、可能な場合はシンボリックリンクの使用も解決します。「..」の解決は、シンボリックリンクの解決の前に発生することに注意してください。この呼び出しは、realpathシステムにシステムコールがない場合(近代的なシステムにはない場合)と同等です。
  • h
    • 末尾のパス名コンポーネントを削除して、頭を残します。これは ' 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で停止する
ステファンChazelas

1
とても助かります、ありがとう。拡張グロブを有効にするには、setopt extended_glob
Shlomi

0

このバージョンのfindupは、@ sinelawの答えのような「find」構文をサポートしますが、realpathを必要としないシンボリックリンクもサポートします。また、オプションの「停止」機能もサポートしているため、これは機能します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プロジェクトにシンボリックリンクを使用しています。$ GOPATH / srcにプロジェクトを作成し、〜/ projectsにシンボリックリンクします。したがって、実行git rev-parse --show-toplevelすると〜/ projectsディレクトリではなく、$ GOPATHディレクトリが出力されます。このソリューションはその問題を解決します。

これは非常に特殊な状況であることを認識していますが、解決策は価値があると思います。


0

Vincent Scheibのソリューションは、ルートディレクトリにあるファイルに対しては機能しません。

次のバージョンでは、最初の引数として開始ディレクトリを渡すことができます。

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
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.