Bash: search up a directory tree

2020-07-11 08:23发布

问题:

I have a source code tree whose root is at something like /home/me/workspace. There are many subdirectories many levels deep. In particular there is a path containing some tools:

/home/me/workspace/tools/scripts

I am writing a bash function which I can call from any place in the tree to which I pass the string tools/scripts. The function should iterate its way from the present working directory to / looking for path fragment tools/scripts, then if it finds it, print out the absolute path in which it is found. In this example, /home/me/workspace would be printed. If the path fragment is not found at all, then nothing is printed.

I already have the following bash function which does this for me:

search_up ()
(
    while [ $PWD != "/" ]; do
        if [ -e "$1" ]; then
            pwd
            break
        fi
        cd ..
    done
)

but this seems a bit long-winded. I am wondering if there are any other ways to do this either in bash itself, or in perhaps a single find command, or any other common utility. I'm particularly looking for readability and brevity.

Note I am not looking for a full recursive search of the entire tree.

Also my bash is not the latest, so please no tricks using the latest, greatest:

$ bash --version
GNU bash, version 3.00.15(1)-release (x86_64-redhat-linux-gnu)
Copyright (C) 2004 Free Software Foundation, Inc.
$

回答1:

This should work but tell me if it needs compatibility with POSIX. The advantage of this is that you don't need to change your directory to higher level just to make the search, and also no need to use a subshell.

#!/bin/bash

search_up() {
    local look=${PWD%/}

    while [[ -n $look ]]; do
        [[ -e $look/$1 ]] && {
            printf '%s\n' "$look"
            return
        }

        look=${look%/*}
    done

    [[ -e /$1 ]] && echo /
}

search_up "$1"

Example:

bash script.sh /usr/local/bin

Output:

/


回答2:

For the record, here is what I ended up using:

while [ $PWD != "/" ]; do test -e tools/bin && { pwd; break; }; cd .. ; done

Similar to my OP, but in the end I was able to drop the subshell parentheses () completely, because this line is itself invoked using the "shell" command from another program. Hence also stuffing it all onto one line.

I still liked KonsoleBox's well-reasoned answer as a possibly more general solution, so I'm accepting that.



标签: bash shell