Executing user defined function in a find -exec call
A function
is local to a shell, so you'd need find -exec
to spawn a shell and have that function defined in that shell before being able to use it. Something like:
find ... -exec ksh -c '
function foo {
echo blan: "$@"
}
foo "$@"' ksh {} +
bash
allows one to export functions via the environment with export -f
, so you can do (in bash):
foo() { ...; }
export -f foo
find ... -exec bash -c 'foo "$@"' bash {} +
ksh88
has typeset -fx
to export function (not via the environment), but that can only be used by she-bang less scripts executed by ksh
, so not with ksh -c
.
Another option is to do:
find ... -exec ksh -c "
$(typeset -f foo)"'
foo "$@"' ksh {} +
That is, use typeset -f
to dump the definition of the foo
function inside the inline script. Note that if foo
uses other functions, you'll also need to dump them as well.
Use \0 as a delimiter and read the filenames into the current process from a spawned command, like so:
foo() {
printf "Hello {%s}\n" "$1"
}
while read -d '' filename; do
foo "${filename}" </dev/null
done < <(find . -maxdepth 2 -type f -print0)
What's going on here:
read -d ''
reads until the next \0 byte, so you don't have to worry about strange characters in filenames.- similarly,
-print0
uses \0 to terminate each generated filename instead of \n. cmd2 < <(cmd1)
is the same ascmd1 | cmd2
except that cmd2 is run in the main shell and not a subshell.- the call to foo is redirected from
/dev/null
to ensure it doesn't accidentally read from the pipe. $filename
is quoted so the shell doesn't try to split a filename that contains whitespace.
Now, read -d
and <(...)
are in zsh, bash and ksh 93u, but I'm not sure about earlier ksh versions.
This is not always applicable, but when it is, it's a simple solution. Set the globstar
option (set -o globstar
in ksh93, shopt -s globstar
in bash ≥4; it's on by default in zsh). Then use **/
to match the current directory and its subdirectories recursively.
For example, instead of find . -name '*.txt' -exec somecommand {} \;
, you can run
for x in **/*.txt; do somecommand "$x"; done
Instead of find . -type d -exec somecommand {} \;
, you can run
for d in **/*/; do somecommand "$d"; done
Instead of find . -newer somefile -exec somecommand {} \;
, you can run
for x in **/*; do
[[ $x -nt somefile ]] || continue
somecommand "$x"
done
When **/
doesn't work for you (because your shell doesn't have it, or because you need a find
option that doesn't have a shell analogue), define the function in the find -exec
argument.