Sep 232012
 

In a former article I’ve wrote about the command locate, an useful command to find quickly a file in your computer.
An alternative to locate is the command find : GNU find searches the directory tree rooted at each given file name by evaluating the given expression from left to right, according to the rules of precedence, until the outcome is known (the left hand side is false for and operations, true for or), at which point find use the defined action and moves on to the next file name.

find can use many options to compose an expression and as standard action it print in the standard output the file name that match the expression.




But before showing some useful examples with find and the exec action a bit of theory:

Find options

The most common options in find to search files are:

-name pattern This is the most common option used and search for the files whose base of file name (the path with the leading directories removed) matches shell the pattern.

-mtime n File’s data was last modified n*24 hours ago.

-size n[cwbkMG] File uses n units of space. The following suffixes can be used:

  • `b’ for 512-byte blocks (this is the default if no suffix is used)
  • `c’ for bytes
  • `w’ for two-byte words
  • `k’ for Kilobytes (units of 1024 bytes)
  • `M’ for Megabytes (units of 1048576 bytes)
  • `G’ for Gigabytes (units of 1073741824 bytes)

-uid n File’s numeric user ID is n.

Find actions

It’s possible to define actions to do on the files that match the search expressions, and among the actions the more versatile is for sure the exec

-exec command ; Execute command; true if 0 status is returned. All following arguments to find are taken to be arguments to the command until an argument consisting of `;’ is encountered. The string `{}’ is replaced by the current file name being processed everywhere it occurs in the arguments to the command,.

Some examples with find and exec

Search files with find and delete them with exec, this is probably one of the most common actions with exec, and you should not use exec for this, read later, here are some examples of common uses:

Search all files with .old extension and delete them:

 find / -name "*.old" -exec /bin/rm {} \;

Search all files with size > of 100 MB and delete them:

 find / -size +100M -exec /bin/rm {} \;

Sometimes some programs goes wild and create thousands of small files into one directoy, in this case you cannot use a simple rm * because the shell would not be able to manages the expansion of the character * with all these file names, but you can use find to delete all files in a directory one by one.

 find . -exec /bin/rm {} \;

Please note, that you should NOT use these examples, In case of deletion of a file GNU find has the option -delete which is safer then “-exec /bin/rm {} \;”. For example:

find / -name "*.old" -delete

In older Unix system you could not have the -delete option, and so you have no choice but to use the -exec action.


And now some more examples of things that you can do with find and the exec action.

Recursively change permissions on files, leave directories alone.

find ./ -type f -exec chmod 644 {} \;

With the option -type f you select only the files and after that is easy to do a chmod on them.

Recursively change the ownership of all the files from olduser to newuser

find / -user olduser  -type f  -exec chown newuser {} \;

In this example I’ve used the search expression -user, as alternative is possible to use -uid

Recursively change the permissions of all, and only, the directory

find . -type d -exec chmod 755 {} \;

In this example I’ve used again the option -type with d parameter to identify only the directories.

Conclusions

As you have seen in these examples the find command with the exec action can achieve really powerful tasks, when you have to do a specific action only on a subset of files this can be the winning combination for you.

Popular Posts:

flattr this!

  12 Responses to “Linux shell, how to use the exec option in find with examples”

  1. All of the -exec example end with “{} \;” which means they would be more efficient and faster if they ended with “{} +” instead. Using ‘+’ instead of ‘;’ makes find aggregate pathnames and execute far fewer commands, instead of one command for each pathname.

    You can’t use a ‘+’ if the last command argument is not “{}”, for example you can’t do:

    find . -name “*.old” -exec mv {} oldfiles + # doesn’t work

    but there is a way around that involving the shell:

    find . -name “*.old” -exec sh -c ‘mv “$@” oldfiles’ sh {} +

    This uses two process per aggregated set of pathnames, but is still way more efficient than:

    find . -name “*.old” -exec mv {} oldfiles \;

    if there are more than a couple of files.

  2. Hi Great article. I am trying to put all my avi movies into a folder of the same name. I am happy to copy them into the directory by hand but was hoping to use find to create the directories:

    find ./ -maxdepth 1 -name “*.avi” -exec mkdir {} \;

    but it will not allow me to create a directory with the same name as the file. Any ideas how I can get around this. Thanks.

  3. To b1ackmai1er:

    Assuming that you are using BASH, I would do it in two steps

    First, load all filenames into a bash array

    IFS=$’\n’ A=( $(find . -maxdepth 1 -name \*.avi ) )

    The purpose of the IFS is to insure that ‘whitespaces’ are not interpreted by bash as a separator (e.g. without it the file ‘hello world.avi’ would become ‘hello’ and ‘wold.avi’

    You can then check the content of the array A with

    for i in “${A[@]}” ; do echo “$i” ; done

    Moving each file into a directory with the same name requires some kinds of renaming since the file and the directory cannot exist with the same name.

    Something like that could work:

    for i in “${A[@]}” ; do echo mkdir “$i.dir” && echo mv “$i” “$i.dir” && echo mv “$i.dir” “$i” ; done

    If the result looks good then just remove the 3 ‘echo’. Do not try to execute the command printed by the echo because they lack proper “quotes”

  4. find -exec is indeed useful… my of my favorites is for finding a list of files that contain a word or phrase….

    find . -exec grep -ls SearchMe {} +

    @Geoff The + .vs ; is a nice touch.

  5. Nice article, but …..

    It’s not the problem it use to be with legacy Unix (Yea Linux!), but I am surprised you didn’t mention that that the -exec option can over flow the command line if find returns too many objects. That’s where xargs comes in:

    # untested, be careful!
    find . -type f -name “*.txt”|xargs rm

    Back in the day, I wrote about it for the new defunct Sys Admin:

    http://anselmo.homeunix.net/SysAdmin-Journal/html/v12/i06/a9.htm

    • Ed wrote:

      I am surprised you didn’t mention that that the -exec option can over flow the command line if find returns too many objects

      That should not happen, which is the whole point of -exec. If you can reproduce this problem with GNU find, please report it (with clear, reproducible instructions on how to reproduce the problem!) as a bug. Thanks.

    • Ed, you are mixing up two different problems. The overflow problem is with -print and command substitution.
      The problem with -exec, as stated in the article you referred to, was efficiency. The original solution to that was xargs, but these days find has that functionality built in – you use ‘+’ instead of ‘;’ to terminate the -exec command – and is preferred because of the problems xargs has with spaces and other special characters in filenames. (GNU solved the xargs problem a different way by inventing find -print0 and xargs -0, but those aren’t as widely implemented as find’s “-exec command {} +”, and they are less efficient because of the extra xargs process and the I/O through the pipe.)

  6. Thanks for this ! I’m loving the CLI !

    Question: How can I use find to search for multiple files in one go, and then have the -exec apply to all of them ?

    For example, I tried: find / -name “*.bak” -o -name “*.bak2″ -o -name “*.backup” -exec ls -l {} \;

    The result listed only the file found by the last -name, “*.backup”, instead of the complete list.

    Also, I am pretty sure there must be a cleaner way to search multiple filenames without repeating the -name. Any pointers ?

    Also, why do we have to escape the “;” if it is what the -exec command is looking for to complete ?

    Thanks for your help !

  7. @Fouad:

    Please allow me to answer your question. You need to surround the or’ed -names options with escaped parenthesis as such:

    find . \( -name “*.bak” -o -name “*.bak2″ -o -name “*.backup” \) -exec ls -l {} \;

    or if using xargs:

    find . \( -name “*.bak” -o -name “*.bak2″ -o -name “*.backup” \) | xargs ls -l

    I’ve never thought about it, but I think you are right about the \; ending the -exec option.

  8. Thanks for your reply Ed, however I get an error stating it is an ” invalid expression: you have used a binary operator “-o” with nothing before it”

    But then it goes on to list a few folders that have nothing to do with the criteria I asked for.

    While I’m asking, if you don’t mind, how could I specify that all the results should be files and not folders ?

    I tried -type f before the \(-name “*bak” etc) bit, but it told me the search path had to precede \(-name, I tried it after, it gave me the same invalid expression error, but still listed the folders.

    Thanks again for your help !

  9. @James:

    OK, I will do so. And I’ll look into the new find-exec functionality

    @Fauad:

    I think your problem is that you need spaces around the parenthesis: \( \)

    find . \( -name “*.bak” -o -name “*.bak2″ -o -name “*.backup” \) -exec ls -l {} \;

  10. That was indeed the problem !

    Live & Learn :), in this case Ask & Learn !

    Thanks a lot Ed !

 Leave a Reply

(required)

(required)


*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>