7

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.)
0

4 Answers 4

8

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"' - {} \;
Sign up to request clarification or add additional context in comments.

Comments

6

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.

5 Comments

I'd write that as -d ''; writing -d $'\0' implies that the shell can actually represent a NUL inside a string (except as a terminator), which it doesn't and can't.
That said -- I tend to much prefer this option, which is more efficient than launching a bash instance per file found, as the other currently proposed answer does.
In testing, I found a for loop much easier to read. For example: ,, for result in `find "base" -name "keyword"`; do echo $result; done
That method is not whitespace safe. It will fail on file names with spaces. There's a reason for everything I did up above, as the answer explains, in detail.
while … ; do … ; done < <( find … ) pattern is recommended. here is why.
3

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

3 Comments

Nice, but unfortunately it doesn’t work with the macOS-builtin bash (see apple.stackexchange.com/questions/291287/…).
You should almost certainly not be relying on macOS's built-in bash to run bash scripts. Install something updated in the last 15 years, or target zsh instead.
That’s fair :-)
1

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.

2 Comments

Why a nested declare -f instead of export -f?
@JohnKugelman, because exported functions aren't compatible between all versions of bash due to shellshock patching. If the shell you invoke isn't the same version as the shell you're in, there's no guarantee it will actually honor your exported function.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.