I'm on Solaris 10 and I have tested the following with ksh (88), bash (3.00) and zsh (4.2.1).

The following code doesn't yield any result:

function foo {
    echo "Hello World"

find somedir -exec foo \;

The find does match several files (as shown by replacing -exec ... with -print), and the function works perfectly when called outside from the find call.

Here's what the man find page say about -exec:

 -exec command       True if the executed command  returns  a
                     zero  value  as  exit status. The end of
                     command must be punctuated by an escaped
                     semicolon (;).  A command argument {} is
                     replaced by the current pathname. If the
                     last  argument  to  -exec  is {} and you
                     specify + rather than the semicolon (;),
                     the command is invoked fewer times, with
                     {} replaced by groups of  pathnames.  If
                     any  invocation of the command returns a
                     non-zero  value  as  exit  status,  find
                     returns a non-zero exit status.

I could probably get away doing something like this:

for f in $(find somedir); do

But I'm afraid of dealing with field separator issues.

Is it possible to call a shell function (defined in the same script, let's not bother with scoping issues) from a find ... -exec ... call?

I tried it with both /usr/bin/find and /bin/find and got the same result.

  • did you try exporting the function after you declare it? export -f foo
    – h3rrmiller
    Oct 12 '12 at 13:41
  • 2
    You will need to make the function an external script and put it in PATH. Alternatively, use sh -c '...' and both define AND run the function in the ... bit. It may help to understand the differences between functions and scripts.
    – jw013
    Oct 12 '12 at 13:49

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.

  • 2
    Can you explain why there are two occurrences of ksh or bash in the -exec command? I understand the first, but not the second occurrence. Oct 15 '12 at 8:13
  • 6
    @danielkullmann In bash -c 'some-code' a b c, $0 is a, $1 is b..., so if you want $@ to be a, b, c you need to insert something before. Because $0 is also used when displaying error messages, it's a good idea to use the name of the shell, or something that makes sense in that context. Oct 15 '12 at 8:34
  • @StéphaneChazelas, thank you for such nice answer. Jan 20 '19 at 20:58

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 as cmd1 | 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.

  • much faster than standard answer but slightly less than using a for loop: time find . -type d -exec bash -c 'dosomething "$0"' {} \; real 0m16.102s time while read -d '' filename; do dosomething "${filename}" </dev/null; done < <(find . -type d -print0) real 0m0.355s time for dir in $(find . -type d); do dosomething $dir; done real 0m0.339s (1465 dirs, on standard hard drive armv7l GNU/Linux synology_armada38x_ds218j) May 31 at 21:37
  • readable benchmark here May 31 at 22:04

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"

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.

  • I cannot seem to find a globstar option on the version of ksh (ksh88) I'm using.
    – rahmu
    Oct 16 '12 at 10:04
  • @rahmu Indeed, it's new in ksh93. Doesn't Solaris 10 have a ksh93 somewhere? Oct 16 '12 at 10:08
  • According to this blog post ksh93 has been introduced in Solaris 11 to replace both the Bourne Shell and ksh88...
    – rahmu
    Oct 16 '12 at 13:39
  • @rahmu. Solaris has had ksh93 for a while as dtksh (ksh93 with some X11 extensions), but an old version and possibly part of an optional package where CDE is optional. Jan 23 '13 at 6:56
  • It should be noted that recursive globbing differs from find in that it excludes dotfiles and doesn't descend into dotdirs and that it sorts the file list (both of which can be addresses in zsh through globbing qualifiers). Also with **/* as opposed to ./**/*, filenames may start with -. Jan 23 '13 at 6:58

if you want a child process, spawned from your script, to use a pre-defined shell function you need to export it with export -f <function>

NOTE: export -f is bash specific

since only a shell can run shell functions:

find / -exec /bin/bash -c 'function "$1"' bash {} \;

EDIT: essentially your script should resemble this:

function foo() { do something; }
export -f foo
find somedir -exec /bin/bash -c 'foo "$0"' {} \;
  • 1
    export -f is a syntax error in ksh, and prints the function definition to the screen in zsh. In all of ksh, zsh and bash, it does not solve the problem.
    – rahmu
    Oct 12 '12 at 13:48
  • 1
    find / -exec /bin/bash -c 'function' \;
    – h3rrmiller
    Oct 12 '12 at 13:54
  • Now it works, but only with bash. Thank you!
    – rahmu
    Oct 12 '12 at 14:42
  • 4
    Never embed {} in the shell code! That means the file name is interpreted as shell code so is very dangerous Jan 6 '15 at 15:03

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.