Take the 2-minute tour ×
Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems.. It's 100% free, no registration required.

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
    foo
done

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.

share|improve this question
    
did you try exporting the function after you declare it? export -f foo –  h3rrmiller Oct 12 '12 at 13:41
1  
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

4 Answers 4

up vote 8 down vote accepted

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 {} +

Some versions of some implementations of ksh on some systems allow to export functions in the environment under some obscure conditions. bash allows it with export -f, so you can do (in bash):

foo() { ...; }
export -f foo
find ... -exec bash -c 'foo "$@"' bash {} +
share|improve this answer
    
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. –  daniel kullmann Oct 15 '12 at 8:13
1  
@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. –  Stéphane Chazelas Oct 15 '12 at 8:34
    
Thanks! I didn't think of that.. –  daniel kullmann Oct 15 '12 at 18:57

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 "{}"' \;

EDIT: essentially your script should resemble this:

#!/bin/bash
function foo() { do something; }
export -f foo
find somedir -exec /bin/bash -c 'foo "{}"' \;
share|improve this answer
    
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

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.

share|improve this answer
    
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? –  Gilles 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. –  Stéphane Chazelas 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 -. –  Stéphane Chazelas Jan 23 '13 at 6: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.

share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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