How to traverse directories a path consists of in BASH

452 Views Asked by At

I'm about to program a file parser which is operating in a directory tree structure. Once I find a specific leaf directory I want to go through all directories which the path consists of and do some operations within them.

Let's say the path is: /d1/d2/d3. Now I want to check whether or not a file x is present in /d1, /d1/d2 and /d1/d2/d3 respectively and in that order.

Of course, one could do something like this:

fields=`find $base_dir -name "leaf_directory" | grep  -o "/" | wc -l`

[[ $fields > 0 ]] || exit 1

for (( i=1; i <= $fields + 1; i++ )) do
    current_dir="`find $base_dir -name "leaf_directory" | cut -d "/" -f $i`"
    source_path="$source_path$current_dir/"

    if [ -f $source_path$file ]; then
        # do sth.
    fi  
done

But is there any more elegant solution for this?

Thank you.

3

There are 3 best solutions below

4
tshiono On BEST ANSWER

Please try the following:

path="aa/bb/cc"
file="x"

while true; do
    if [[ -f "$path/$file" ]]; then
        echo "Found: $path/$file"
    fi
    if [[ $path =~ ^/?[^/]+$ ]]; then
        break
    fi
    path="${path%/*}"
done

It comes down to the problem how to generate upper directories from the given path. My code above will work for the both cases of absolute path and relative path. In order to accept the path which starts with . or .., a small modification in the regexp will be needed.

[EDIT]

If you want to process in the order as aa, aa/bb, .., please try the following:

path="aa/bb/cc"
file="x"

while true; do
    array+=("$path")
    if [[ $path =~ ^/?[^/]+$ ]]; then
        break
    fi
    path="${path%/*}"
done
for (( i=${#array[@]}-1; i>=0; i-- )); do
    p="${array[$i]}"
    if [[ -f "$p/$file" ]]; then
        echo "Found: $p/$file"
    fi
done

[EDIT]

If you want to include the root directory / in the search path when an absolute path is specified, please try:

path="/aa/bb/cc"
file="x"

while true; do
    array+=("$path")
    if [[ $path =~ ^(/|[^/]+)$ ]]; then
        break
    elif [[ $path =~ ^/[^/]+$ ]]; then
        path="/"
    else
        path="${path%/*}"
    fi
done
for (( i=${#array[@]}-1; i>=0; i-- )); do
    p="${array[$i]}"
    if [[ -f "$p/$file" ]]; then
        echo "Found: $p/$file"
    fi
done
0
Ed Morton On

Is this what you're trying to do (untested)?

fullPath='first/second/third'
mapfile -t -d '/' dirs <<<"${fullPath}/"
numDirs=$(( ${#dirs[@]} - 1 ))
path=''
file='x'
for (( dirNr=1; dirNr<=numDirs; dirNr++ )); do
    path="${path}${dirs[$dirNr]}/"
    if [[ -f "${path}${file}" ]]; then
        printf 'Found "%s"\n' "${path}${file}"
    fi
done
0
adob On

Now I found another solution using the internal field separator. The / directory is also considered.

path="/d1/d2/d3"
file=x

IFS='/'
unset current_path
for current_dir in $path; do
    current_path="${current_path}${current_dir}/"
    if [ -f "${current_path}${file}" ]; then
        printf '%s\n' "$current_path"
    fi  
done
unset IFS