41

I understand that the -exec can take a + option to mimic the behaviour of xargs. Is there any situation where you'd prefer one form over the other?

I personally tend to prefer the first form, if only to avoid using a pipe. I figure surely the developers of find must've done the appropriate optimizations. Am I correct?

rahmu
  • 20,023

4 Answers4

28

Safely piping file names to xargs requires that your find supports the -print0 option and your xargs has the corresponding option to read it (--null or -0). Otherwise, filenames with unprintable characters or backslashes or quotes or whitespace in the name may cause unexpected behavior. On the other hand, find -exec {} + is in the POSIX find spec, so it is portable, and it is about as safe as find -print0 | xargs -0, and definitely safer than find | xargs. I'd recommend never doing find | xargs without -print0.

jw013
  • 51,212
  • 7
    A notable exception to the portability of find … -exec … {} + is OpenBSD, which only acquired this feature with version 5.1 released in 2012. All BSDs have had -print0 for several years, even OpenBSD (though it resisted that feature for a while too). Solaris, on the other hand, sticks to POSIX features, so you get -exec + and no -print0. – Gilles 'SO- stop being evil' Jun 27 '12 at 23:07
  • -print0 is a pain and though you can argue xargs --delimiter "\n" isn't equivalent, I've never once used the former after discovering the latter. – Sridhar Sarnobat Jul 28 '15 at 17:27
  • 4
    I don't see how -0 is more of a pain than --delimiter "\n". – jw013 Jul 28 '15 at 21:54
  • 4
    In addition to -0, GNU xargs needs -r to avoid running the command if there's no input. – Stéphane Chazelas Oct 11 '15 at 20:19
  • 3
    Another problem of | xargs -r0 cmd is that cmd's stdin is affected (depending on the xargs implementation, it's /dev/null or the pipe. – Stéphane Chazelas Oct 11 '15 at 20:20
25

You might want to chain calls to find (once, when you learned, that it is possible, which might be today). This is, of course, only possible as long as you stay in find. Once you pipe to xargs it's out of scope.

Small example, two files a.lst and b.lst:

cat a.lst
fuddel.sh
fiddel.sh

cat b.lst
fuddel.sh

No trick here - simply the fact that both contain "fuddel" but only one contains "fiddel".

Assume we didn't know that. We search a file which matches 2 conditions:

find -exec grep -q fuddel {} ";" -exec grep -q fiddel {} ";" -ls
192097    4 -rw-r--r--   1 stefan   stefan         20 Jun 27 17:05 ./a.lst

Well, maybe you know the syntax for grep or another program to pass both strings as condition, but that's not the point. Every program which can return true or false, given a file as argument, can be used here - grep was just a popular example.

And note, you may follow find -exec with other find commands, like -ls or -delete or something similar. Note, that delete not only does rm (removes files), but rmdir (removes directories) too.

Such a chain is read as an AND combination of commands, as long as not otherwise specified (namely with an -or switch (and parens (which need masking))).

So you aren't leaving the find chain, which is a handy thing. I don't see any advantage in using -xargs, since you have to be careful in passing the files, which is something find doesn't need to do - it automatically handles passing each file as a single argument for you.

If you believe you need some masking for finds {} braces, feel free to visit my question which asks for evidence. My assertion is: You don't.

mas
  • 1,909
  • 2
  • 18
  • 32
user unknown
  • 10,482
11

If you use the -exec ... ; form (remembering to escape the semicolon), you're running the command once per filename. If you use -print0 | xargs -0, you run multiple commands per filename. You should definitely use the -exec + form, which puts multiple files in a single command line and is much faster when a large number of files is involved.

A big plus of using xargs is the ability to run multiple commands in parallel using xargs -P. On multi-core systems, that can provide huge time savings.

Alexios
  • 19,157
  • 8
    You meant -P instead of -p. Keep in mind xargs -P is not in the POSIX standard, whereas find -exec {} + is, which is important if you are going for portability. – jw013 Jun 27 '12 at 12:54
  • @Alexios You don't have to escape the plus sign, because it does not have a special meaning for the shell: find /tmp/ -exec ls "{}" + works just fine. – daniel kullmann Jun 27 '12 at 13:48
  • 1
    Corrent, of course. I've been escaping everything after the -exec for so long (I'm a masochist, I don't even use quotes to escape {}, I always type \{\}; don't ask), everything looks like it must be escaped now. – Alexios Jun 27 '12 at 14:17
  • 1
    @Alexios: If you find an example (except from being a masochist) where the masking of the braces is useful, please provide it as an answer to my question here - afaik this hint is outdated and only a relict in the manpage. I've never seen an example where find /tmp/ -exec ls {} + wouldn't work. – user unknown Jun 27 '12 at 15:00
  • 1
    For me, this is muscle memory formed in the days of SunOS 4. Since I've been doing it for years, I completely failed to notice when bash started accepting the braces verbatim. I'm pretty sure at least one of the old shells I've used threw hissy fits if the braces weren't escaped. – Alexios Jun 27 '12 at 15:05
  • @Alexios - To clarify, do you mean that if the command -exec mv {} ; is run, it would execute as mv file1, mv file2 and so on? In contrast xargs -0 mv could result in xargs mv file``, xargs mv file2` and thus multiple commands? – Ryan Jan 19 '20 at 02:31
10

Concerning performance I thought that -exec … + would simply be better because it's a single tool doing all the work but a part of GNU findutil's documentation says that -exec … + might be less efficient in some cases:

[find with -exec … +] can be less efficient than some uses of xargs; for example xargs allows new command lines to be built up while the previous command is still executing, and allows you to specify a number of commands to run in parallel. However, the find ... -exec ... + construct has the advantage of wide portability. GNU findutils did not support ‘-exec ... +’ until version 4.2.12 [January 2005]; one of the reasons for this is that it already had the ‘-print0’ action in any case.

I wasn't exactly sure what that meant so I asked in the chat where derobert explained it as:

find probably could continue to search for the next batch of files while the -exec … + is running, but it doesn't.
find … | xargs … does, because then the find is a different process, and it keeps running until the pipe buffer fills up

(Formatting by me.)

So there is that. But if performance really matters you would have to do realistic benchmarking or rather even ask yourself whether you even would want to use shell for such cases.

Here on this site I think it's better to advise people to use the -exec … + form whenever possible because just because it's simpler and for the reasons mentioned in the other answers here (e.g. handling strange filenames without having to think much).

phk
  • 5,953
  • 7
  • 42
  • 71