Take the 2-minute tour ×
Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems.. It's 100% free, no registration required.

I'm nested deep in a file tree, and I'd like to find which parent directory contains a file.

E.g. I'm in a set of nested Git repositories and want to find the .git directory controlling the files I'm currently at. I'd hope for something like find -searchup -iname ".git"

share|improve this question

5 Answers 5

up vote 2 down vote accepted

An even more general version that allows using find options:

set -e
shift 1
while [[ "`readlink -f $path`" != "/" ]];
    find "$path"  -maxdepth 1 -mindepth 1 "$@"

For example (assuming the script is saved as find_up.sh)

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

...will print the names of all of some_dir's ancestors (including itself) up to / in which a file with the pattern is found.

share|improve this answer
git rev-parse --show-toplevel

will print out the top level directory of the current repository, if you are in one.

Other related options:

# `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
share|improve this answer
This works only for git itself. The question is more generic. –  alex Jan 26 '11 at 7:15
This will not work in a bare repo, –  Jason Pyeron Sep 1 '14 at 4:01

Find can't do it. I can't think of anything simpler than a shell loop. (Untested, assumes there is no /.git)

git_root=$(pwd -P 2>/dev/null || command pwd)
while [ ! -e "$git_root/.git" ]; do
  if [ "$git_root" = "" ]; then break; fi

For the specific case of a git repository, you can let git do the work for you.

git_root=$(GIT_EDITOR=echo git config -e)
share|improve this answer
Cool, this puts me on a path for a general solution - which I post as another answer here. –  Vincent Scheib Oct 7 '11 at 0:56
I also like the answer being general and not specific to git. –  Johann Philipp Strathausen Nov 25 '11 at 13:47

A generalized version of Gilles' answer, first parameter used to find match:

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

Keeps the use of sym-links.

share|improve this answer

If you're using zsh with extended globbing enabled, you can do it with a oneliner:

(../)#.git(:h)  # relative path to containing directory, eg. '../../..', '.'
(../)#.git(:a)  # absolute path to actual file, eg. '/home/you/src/prj1/.git'
(../)#.git(:ah) # absolute path to containing directory, eg. '/home/you/src/prj1'

Explanation (quoted from man zshexpn):

Recursive Globbing

A pathname component of the form (foo/)# matches a path consisting of zero or more directories matching the pattern foo. As a shorthand, **/ is equivalent to (*/)#.


After the optional word designator, you can add a sequence of one or more of the following modifiers, each preceded by a ':'. These modifiers also work on the result of filename generation and parameter expansion, except where noted.

  • a
    • Turn a file name into an absolute path: prepends the current directory, if necessary, and resolves any use of '..' and '.'
  • A
    • As 'a', but also resolve use of symbolic links where possible. Note that resolution of '..' occurs before resolution of symbolic links. This call is equivalent to a unless your system has the realpath system call (modern systems do).
  • h
    • Remove a trailing pathname component, leaving the head. This works like 'dirname'.

Credits: Faux on #zsh for the initial suggestion of using (../)#.git(:h).

share|improve this answer
This is amazing... If I could only figure out (hint: you should tell me) what (../) is doing... Oh, and who/what is Faux on #zsh? –  alex gray Mar 20 at 15:05
@alexgray (../) alone doesn't mean much, but (../)# means try to expand the pattern inside the parenthesis 0 or more times, and only use the ones that actually exist on the filesystem. Because we're expanding from 0 to n parent directories, we effectively search upwards to the root of the file system (note: you probably want to add something after the pattern to make it meaningful, but for enlightenment execute print -l (../)#). And #zsh is an IRC channel, Faux is a username on it. Faux suggested the solution, therefore the credit. –  unthought Mar 22 at 11:03

Your Answer


By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.