Execute bash function from find command

3.5k views Asked by At

I have defined a function in bash, which checks if two files exists, compare if they are equal and delete one of them.

function remodup {
    F=$1
    G=${F/.mod/}
    if [ -f "$F" ] && [ -f "$G" ]
    then
        cmp --silent "$F" "$G" && rm "$F" || echo "$G was modified"
    fi
}

Then I want to call this function from a find command:

find $DIR -name "*.mod" -type f -exec remodup {} \;

I have also tried | xargs syntax. Both find and xargs tell that ``remodup` does not exist.

I can move the function into a separate bash script and call the script, but I don't want to copy that function into a path directory (yet), so I would either need to call the function script with an absolute path or allways call the calling script from the same location.

(I probably can use fdupes for this particular task, but I would like to find a way to either

  1. call a function from find command;
  2. call one script from a relative path of another script; or
  3. Use a ${F/.mod/} syntax (or other bash variable manipulation) for files found with a find command.)
4

There are 4 answers

5
John Kugelman On BEST ANSWER

You could manually loop over find's results.

while IFS= read -rd $'\0' file; do
    remodup "$file"
done < <(find "$dir" -name "*.mod" -type f -print0)

-print0 and -d $'\0' use NUL as the delimiter, allowing for newlines in the file names. IFS= ensures spaces as the beginning of file names aren't stripped. -r disables backslash escapes. The sum total of all of these options is to allow as many special characters as possible in file names without mangling.

0
anubhava On

You need to export the function first using:

export -f remodup

then use it as:

find $DIR -name "*.mod" -type f -exec bash -c 'remodup "$1"' - {} \;
2
Charles Duffy On

To throw in a third option:

find "$dir" -name "*.mod" -type f \
  -exec bash -s -c "$(declare -f remodup)"$'\n'' for arg; do remodup "$arg"; done' _ {} +

This passes the function through the argv, as opposed to through the environment, and (by virtue of using {} + rather than {} ;) uses as few shell instances as possible.


I would use John Kugelman's answer as my first choice, and this as my second.

3
chepner On

Given that you aren't using many features of find, you can use a pure bash solution instead to iterate over the desired files.

shopt -s globstar nullglob
for fname in ./"$DIR"/**/*.mod; do
    [[ -f $fname ]] || continue
    f=${fname##*/}
    remodup "$f"
done