3

I'd like to find a way to find a given file by looking upward in the directory structure, rather than recursively searching through child directories.

There's a node module that appears to do exactly what I want, but I don't want to depend on installing JavaScript or packages like that. Is there a shell command for this? A way to make find do that? Or a standard approach that I just wasn't able to find through Googling?

1
  • Do you also want this behavior of that node module? "If X/.dir/file.ext exists, return it." (that searches an immediate sub directory of X, not purely ancestors)
    – Jeff Schaller
    Jul 2 '16 at 14:27
5

This is a direct translation of the find-config algorithm in generic shell commands (tested under bash, ksh, and zsh), where I use a return code of 0 to mean success and 1 to mean NULL/failure.

findconfig() {
  # from: https://www.npmjs.com/package/find-config#algorithm
  # 1. If X/file.ext exists and is a regular file, return it. STOP
  # 2. If X has a parent directory, change X to parent. GO TO 1
  # 3. Return NULL.

  if [ -f "$1" ]; then
    printf '%s\n' "${PWD%/}/$1"
  elif [ "$PWD" = / ]; then
    false
  else
    # a subshell so that we don't affect the caller's $PWD
    (cd .. && findconfig "$1")
  fi
}

Sample run, with the setup stolen copied and extended from Stephen Harris's answer:

$ mkdir -p ~/tmp/iconoclast
$ cd ~/tmp/iconoclast
$ mkdir -p A/B/C/D/E/F A/good/show 
$ touch A/good/show/this A/B/C/D/E/F/srchup A/B/C/thefile 
$ cd A/B/C/D/E/F
$ findconfig thefile
/home/jeff/tmp/iconoclast/A/B/C/thefile
$ echo "$?"
0
$ findconfig foobar
$ echo "$?"
1
5
  • Why is .dir hard-coded into the function?
    – iconoclast
    Jan 22 '19 at 17:20
  • @iconoclast on account of the spec referenced in the question: npmjs.com/package/find-config
    – Jeff Schaller
    Jan 22 '19 at 17:23
  • I don't think the .dir given here is meant to be taken literally. I certainly hope not, because that would make this NPM package not very useful.
    – iconoclast
    Jan 22 '19 at 19:00
  • It's possible I misunderstood, then! I'm not familiar with node.js. Is it your understanding that the code is checking for the file in the/any immediate subdirectory?
    – Jeff Schaller
    Jan 22 '19 at 19:05
  • 1
    it's just checking in the current directory and its parents. So I think we can just remove the second line of code inside the function, and it looks like it should work nicely.—I just updated the code (and tested it first, briefly) but I didn't update the sample run
    – iconoclast
    Jan 22 '19 at 20:15
4

One way to do it:

#! /bin/sh
dir=$(pwd -P)
while [ -n "$dir" -a ! -f "$dir/$1" ]; do
    dir=${dir%/*}
done
if [ -f "$dir/$1" ]; then printf '%s\n' "$dir/$1"; fi

Replace pwd -P by pwd -L if you want to follow symlinks instead of checking physical directories.

2
  • Any reason you use -a? [ -n "$dir" ] && ! [ -f "$dir/$1" ] is more reliable. (Exercise: Try determining correct parsing for [ -n "$a" -a "$b" = "$c" ] if a='=' and b='-o'.)
    – Wildcard
    Jul 2 '16 at 5:19
  • @Wildcard Because I'm an old fart and set in my bad old ways. :) Also, because it avoids running a second [ on shells where [ is not a builtin. As for [ -n "$a" -a "$b" = "$c" ], that's why you always write x"$b" = x"$c" rather than just "$b" = "$c". Jul 2 '16 at 5:30
3

A simple loop of checking the current directory and if it's not found then strip off the last component would work

#!/bin/bash

wantfile="$1"

dir=$(realpath .)

found=""

while [ -z "$found" -a -n "$dir" ]
do
  if [ -e "$dir/$wantfile" ]
  then
    found="$dir/$wantfile"
  fi
  dir=${dir%/*}
done

if [ -z "$found" ]
then
  echo Can not find: $wantfile
else
  echo Found: $found
fi

For example, if this is the directory tree:

$ find /tmp/A
/tmp/A
/tmp/A/good
/tmp/A/good/show
/tmp/A/good/show/this
/tmp/A/B
/tmp/A/B/C
/tmp/A/B/C/thefile
/tmp/A/B/C/D
/tmp/A/B/C/D/E
/tmp/A/B/C/D/E/F
/tmp/A/B/C/D/E/F/srchup

$ pwd      
/tmp/A/B/C/D/E/F

$ ./srchup thefile
Found: /tmp/A/B/C/thefile

We can see that the search went up the tree until it found what we were looking for.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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