13

So say I'm in a directory that is deep within a file structure, such as

/home/cory/projects/foo/bar/bash/baz

I know that in either the current folder baz or one of the folders above me (such as home, cory, projects, foo, bar, or bash) there is a file named happy. How can I find that file without searching a lot of extra folders?

Cory Klein
  • 18,911
  • Why is this, older question, closed with a link to a newer one? Looks unreasonable to me - "already has an answer" would better fit to that other, newer question. – user unknown Oct 22 '20 at 23:40
  • I don’t think relative age is the primary determining factor when deciding which to close. The other question does seem to have a bit more activity and through ness in its answers so my guess is that factored into the decision. – Cory Klein Oct 22 '20 at 23:44
  • But you don't know whether the more on traffic is an effect of closing this question. :) – user unknown Oct 22 '20 at 23:49

3 Answers3

25
#!/bin/bash

function upsearch () {
    test / == "$PWD" && return || test -e "$1" && echo "found: " "$PWD" && return || cd .. && upsearch "$1"
}

This function will walk up from the current dir. Note, that it is a function, and will change the directory, while traversing. It will stop in the directory, where it finds the file, and will walk up to the root / if not.

You might want to change it to a script instead of a function, and maybe jump back, if the file isn't found in the root directory.

If you never want to cd to the dir:

upsearch () {
  local slashes=${PWD//[^\/]/}
  local directory="$PWD"
  for (( n=${#slashes}; n>0; --n ))
  do
    test -e "$directory/$1" && echo "$directory/$1" && return 
    directory="$directory/.."
  done
}

This will lead to results like /home/cory/a/b/c/../../../happy if the file is in /home/cory/. If you need a cleaned path, you could do some

cd "$directory"
echo "$PWD"
cd - 

on success.

To restrict the search to regular files and excludes directories, symbolic links and the like, you can change the tests to -f instead of -e.

user unknown
  • 10,482
  • Remember to quote your expansions. If $PWD has a space in your test will be broken by wordsplitting. – Chris Down Sep 20 '11 at 15:50
  • I added masking to the directories. Is it correct now? It will still fail with special characters like '*' in filenames, won't it? – user unknown Sep 21 '11 at 02:41
  • Alternatively, without function or recursion: filePath="$(while [ ! -f "happy" -a / != "$PWD" ]; do cd .. ;done; [ / != "$PWD" ] && echo $PWD || echo .)" – Leon S. Aug 18 '20 at 06:13
1

Take a look at the -mindepth and -maxdepth options to find.

find won't traverse directories in a backwards direction, but you can approach it in the regular direction and limit the depth of the search.

For example:

find /home /home/cory /home/cory/projects /home/cory/projects/foo /home/cory/projects/foo/bar /home/cory/projects/foo/bar/bash -maxdepth 0 -name happy -print
gjb
  • 119
  • That's not going to help. Either you use -maxdepth 0 or -maxdepth 1, in which case find isn't useful, or you don't, in which case you traverse plenty of extraneous directories for no reason. – Gilles 'SO- stop being evil' May 19 '11 at 23:02
  • It is a working solution. I have added an example to clarify this. – gjb May 19 '11 at 23:21
  • You're missing the step where you generate the list of directories from $PWD or some argument. And if you're going to do that, you don't need find (ok, you can call it, but it won't be very useful). – Gilles 'SO- stop being evil' May 19 '11 at 23:24
  • The question was "How can I find that file without searching a lot of extra folders?". I accept that this might not be the most elegant solution, but it gets the job done without searching any extra folders. – gjb May 19 '11 at 23:32
1

I improved on @user-unknown's solution a bit by saving the current dir, performing an arbitrary test, then restoring the original dir before returning with response. It's more flexible because you can test for anything.

I also provide for an optional second argument that says how high to go.

https://gist.github.com/1577473

#!/bin/bash

# Use this script instead of ~/.rvm/bin/rvm-prompt
# and the output of rvm-prompt will show up in your command prompt
# only if you are in a Ruby project directory.

# see http://stackoverflow.com/a/4264351/270511
# and http://unix.stackexchange.com/questions/13464/is-there-a-way-to-find-a-file-in-an-inverse-recursive-search

upsearch () {
  the_test=$1
  up_to=$2
  curdir=`pwd`
  result=1

  while [[ "`pwd`" != "$up_to" && "`pwd`" != '/' ]]; do
    if eval "[[ $the_test ]]"; then
      result=0
      break
    fi
    cd ..
  done
  cd $curdir
  return $result
}

if upsearch '-e Gemfile || ! -z "$(shopt -s nullglob; echo *.gemspec *.rb)"' ; then
  ~/.rvm/bin/rvm-prompt
fi
Rob W
  • 928