简体   繁体   中英

bash - recursive script can't see files in sub directory

I got a recursive script which iterates a list of names, some of which are files and some are directories.

If it's a (non-empty) directory, I should call the script again with all of the files in the directory and check if they are legal.

The part of the code making the recursive call:

if [[ -d $var ]] ; then
    if [ "$(ls -A $var)" ]; then 
        ./validate `ls $var`
    fi
fi

The part of code checking if the files are legal:

if [[ -f $var ]]; then
    some code
fi

But, after making the recursive calls, I can no longer check any of the files inside that directory, because they are not in the same directory as the main script, the -f $var if cannot see them.

Any suggestion how can I still see them and use them?

Why not use find ? Simple and easy solution to the problem.

Always quote variables, you never known when you will find a file or directory name with spaces

shopt -s nullglob
if [[ -d "$path" ]] ; then
    contents=( "$path"/* )
    if (( ${#contents[@]} > 0 )); then 
        "$0" "${contents[@]}"
    fi
fi
  • you're re-inventing find
  • of course, var is a lousy variable name
  • if you're recursively calling the script, you don't need to hard-code the script name.
    • you should consider putting the logic into a function in the script, and the function can recursively call itself, instead of having to spawn an new process to invoke the shell script each time. If you do this, use $FUNCNAME instead of "$0"

A few people have mentioned how find might solve this problem, I just wanted to show how that might be done:

find /yourdirectory -type f -exec ./validate {} +; 

This will find all regular files in yourdirectory and recursively in all its sub-directories, and return their paths as arguments to ./validate . The {} is expanded to the paths of the files that find locates within yourdirectory . The + at the end means that each call to validate will be on a large number of files, instead of calling it individually on each file (wherein the + is replaced with a \\ ), this provides a huge speedup sometimes.

One option is to change directory (carefully) into the sub-directory:

 if [[ -d "$var" ]] ; then if [ "$(ls -A $var)" ]; then (cd "$var"; exec ./validate $(ls)) fi fi 

The outer parentheses start a new shell so the cd command does not affect the main shell. The exec replaces the original shell with (a new copy of) the validate script. Using $(...) instead of back-ticks is sensible. In general, it is sensible to enclose variable names in double quotes when they refer to file names that might contain spaces (but see below). The $(ls) will list the files in the directory.

Heaven help you with the ls commands if any file names or directory names contain spaces; you should probably be using * glob expansion instead. Note that a directory containing a single file with a name such as -n would trigger a syntax error in your script.


Corrigendum

As Jens noted in a comment, the location of the shell script ( validate ) has to be adjusted as you descend the directory hierarchy. The simplest mechanism is to have the script on your PATH, so you can write exec validate or even exec $0 instead of exec ./validate . Failing that, you need to adjust the value of $0 — assuming your shell leaves $0 as a relative path and doesn't mess around with converting it to an absolute path. So, a revised version of the code fragment might be:

# For validate on PATH or absolute name in $0
if [[ -d "$var" ]] ; then
    if [ "$(ls -A $var)" ]; then 
        (cd "$var"; exec $0 $(ls))
    fi
fi

or:

# For validate not on PATH and relative name in $0
if [[ -d "$var" ]] ; then
    if [ "$(ls -A $var)" ]; then 
        (cd "$var"; exec ../$0 $(ls))
    fi
fi

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM