5

The man page of Debian 8's find command says:

If the whole expression contains no actions other than -prune or -print, -print is performed on all files for which the whole expression is true.

So why do these outputs differ:

$ mkdir -p test/foo test/bar && cd test && touch foo/bar bar/foo
$ # Test 1
$ find . -name foo -type d -prune -o -name foo
./foo
./bar/foo
$ # Test 2
$ find . -name foo -type d -prune -o -name foo -print
./bar/foo

So test 1: does the expression contain "no actions other than -prune or -print?" Well, excluding the prune, yes that statement is true, there are no actions. So these results are expected since for ./foo the expression before the -o option returns True, so it's printed.

But test 2: does the expression contain "no actions other than -prune or -print?" Well, excluding the prune and the print, yes that statement is true again, there are no other actions. So I would expect the same results.

But I don't get ./foo. Why?

It's as if the man page should read: "If the whole expression contains no actions other than -prune or -print, -print is performed on all files for which the whole expression is true."

artfulrobot
  • 20,637
  • 11
  • 55
  • 81
  • I think it should say `no actions other than -prune.` It certainly doesn't add an implicit `-print` if there is already an explicit one somewhere in the expression. – Reinstate Monica Please May 06 '16 at 09:48

3 Answers3

3

I'm going with the simpler explanation, the man page is wrong. It should instead say

If the whole expression contains no actions other than -prune or -print, -print is performed on all files for which the whole expression is true.

It should also maybe contain a caveat for -quit, which is an action, but it causes -find to exit immediately. So even though an implicit -print is added for the whole expression it is never actually executed.

The posix find man page contains a clearer explanation, though it doesn't have quite as many actions as the expanded gnu version.

If no expression is present, -print shall be used as the expression. Otherwise, if the given expression does not contain any of the primaries -exec, -ok, or -print, the given expression shall be effectively replaced by:

( given_expression ) -print

Out of what gnu calls actions, posix only defines -exec, -ok, -print, and -prune. It does not have any of the expanded actions -delete, -ls, etc... So the definition matches the corrected gnu one by only omitting -prune.

Here are some examples using all the gnu find actions which prove the point. For all consider the following file structure

$ tree
.
└── file

-delete

$ find -name file -delete
$

-exec command ;

$ find -name file -exec echo '-exec is an action so an implicit -print is not applied' \;
-exec is an action so an implicit -print is not applied
$

-execdir command {} +

$ find -name file -exec echo 'This should print the filename twice if an implicit -print is applied: ' {} +
This should print the filename twice if an implicit -print is applied:  ./file
$

-fls

$ find -name file -fls file
$

-fprint

$ find -name file -fprint file
$

-ls

$ find -name file -ls
1127767338    0 -rw-rw-r--   1 user   user          0 May  6 07:15 ./file
$

-ok command ;

$ find -name file -ok echo '-ok is an action so an implicit -print is not applied' \;
< echo ... ./file > ? y
-ok is an action so an implicit -print is not applied
$

-okdir command ;

$ find -name file -okdir echo '-okdir is an action so an implicit -print is not applied' \;
< echo ... ./file > ? y
-okdir is an action so an implicit -print is not applied
$

-print

#./file would be printed twice if an implicit `-print was applied`
$ find -name file -print
./file
$

-print0

#./file would be printed twice if an implicit `-print was applied`
$ find -name file -print0
./file$

-printf

$ find -name file -printf 'Since -printf is an action the implicit -print is not applied\n'
Since -printf is an action the implicit -print is not applied
$

-prune

$ find -name file -prune
./file
$

-quit

$ find -name file -quit
$ find -D opt -name file -quit
...
Optimized command line:
( -name file [0.1] -a [0.1] -quit [1]  ) -a [0.1] -print [1]
Community
  • 1
  • 1
Reinstate Monica Please
  • 11,123
  • 3
  • 27
  • 48
  • Brilliant! Love the exhaustive examples. What about `find -name file -prune -o -prune`? This contains no actions other than `-prune`, so should print, no? But unlike `find -name file -prune`, it does not print `./file`. The file matches the LHS `-name`, and `-prune` returns true, so the expression is solved with the LHS and the RHS should not have effect. But clearly it does. – artfulrobot May 06 '16 at 11:20
  • @artfulrobot It does add the impled `-print`, but it `prunes` the root directory `.`, so it never actually descends to the file. i.e. `find -name file -prune -o ! -name '.' -prune` – Reinstate Monica Please May 06 '16 at 11:29
  • But it processes expressions left to right doesn't it? In which case that 2nd prune will never be reached for file `./file` because the initial match to the left of the `-o` returns true and therefore completes the expression's evaluation. Also, I get `.` printed, just not `./file`. – artfulrobot May 06 '16 at 11:35
  • @artfulrobot Sure, but it never processes `./file` at all. Execute just `find` in that directory. You will see `.` on the first line, and `./file` on the second. Now execute `find -prune`. You will again see only `.` as in your example. In your example, `.` doesn't match the LHS, but it does the RHS, so it just gets pruned as in `find -prune` – Reinstate Monica Please May 06 '16 at 11:41
  • Aaaaohw, I geddit! Of course, because find starts from the top, the root, and so as you say, that gets pruned so it never sees file. And because it only contains prune actions, it's printed. Yes, thank you! – artfulrobot May 06 '16 at 11:52
2

Let's look at this command:

find . -name foo -type d -prune -o -name foo

Since -print is the default action, then this action is applied to the whole set of expressions, i.e. -name foo -type d -prune -o -name foo. So it's the same as the following:

find . \( -name foo -type d -prune -o -name foo \) -print

Now let's look at this command:

find . -name foo -type d -prune -o -name foo -print

According to man find expr1 expr2 has higher priority than expr1 -o expr2. So in the command above two expressions are combined with the OR operator:

  • -name foo -type d -prune
  • -name foo -print

So if you want to apply -print to both, use parentheses:

find . \( -name foo -type d -prune -o -name foo \) -print

But -prune -o RHS implies that RHS is evaluated only for those items which didn't get pruned.

We can check if we are right by running find with -D tree or -D opt:

find -D opt -O0 . -name foo -type d -prune -o -name foo -print
...
 (  ( -name foo [0.1] -a [0.04] [need type] -type d [0.4]  ) -a [0.04] [call stat] [need type] -prune [1]  ) -o [0.14]  ( -name foo [0.1] -a [0.1] -print [1]  ) 
./bar/foo


find -D opt -O0 . -name foo -type d -prune -o -name foo
 (  (  ( -name foo [0.1] -a [0.04] [need type] -type d [0.4]  ) -a [0.04] [call stat] [need type] -prune [1]  ) -o [1] -name foo [0.1]  ) -a [0.14] -print [1] 
./foo
./bar/foo

As we can see, find makes (... -prune) -o (... -print) from the first expression where we put -print explicitly. It makes (...) -a -print from the second expression where we omit -print.

So I think that by "the whole expression" the man page means one of expression parts described in OPERATORS section.

Ruslan Osmanov
  • 20,486
  • 7
  • 46
  • 60
  • I agree with your tests and understand them. But this does not match the man page, AFAICS. In your para starting "Now lets' look..." you are saying that the default action is not applied, but how is that the case compared to the man page since your 'whole expression' does not contain any actions except `-prune` and `-print`, and therefore a default should - according to the man - be applied? – artfulrobot May 05 '16 at 10:18
  • @artfulrobot, perhaps, the man page requires clarification at that point. But it would have been senseless, if `find` had printed everything. We should have an option to prune something, and print the rest. The man page has some examples regarding this problem, by the way. For example: `find . -path ./src/emacs -prune -o -print`, "...skip the directory `src/emacs' and all files and directories under it, and print the names of the other files found..." – Ruslan Osmanov May 05 '16 at 10:46
  • Yes, thanks, I think you're right: when the man page says "whole expression" it means "part of it"! I think Ignacio nailed it succinctly so I've accepted that answer, but many thanks for your input too! – artfulrobot May 05 '16 at 11:12
  • @RuslanOsmanov Good answer up until `So the command above does the same as the following:...`, then it gets a bit unclear. It only does the same thing if nothing gets pruned on the left side of the original. – Reinstate Monica Please May 06 '16 at 09:42
  • @BroSlow, I disagree. Expression `\( -name foo -type d -prune -o -name foo \)` consists of two parts separated by OR. In the 1st expr. `-prune` returns `True`, if it's a directory with name "foo". The 2nd expr. is `True`, if the name is "foo". So the whole expression is `True`, if the name is "foo". Then the command applies `-print` to the group `\( ... \)`. So the command prints all "foo"s, which is the same as `find . -name foo -print`. – Ruslan Osmanov May 06 '16 at 10:51
  • @RuslanOsmanov What happens if you have a directory `foo` that contains a file `foo`? – Reinstate Monica Please May 06 '16 at 10:54
  • @BroSlow, ah, indeed, `-prune` instructs not to descend into directory, if the name is "foo". Removed that part. Thanks. – Ruslan Osmanov May 06 '16 at 10:56
  • @RuslanOsmanov See where you are coming from now, if it was a no-op, agree they are the same. +1, though I still think the confusion here is that the man page is simply wrong. – Reinstate Monica Please May 06 '16 at 10:59
  • @BroSlow, we already came to the conclusion that the man page needs to be fixed. – Ruslan Osmanov May 06 '16 at 11:01
  • @RuslanOsmanov Partially, but it lead to the wrong conclusion about what needs to get changed. Whole expression means whole expression, `Ignacio's` answer is not correct. – Reinstate Monica Please May 06 '16 at 11:06
0

Check the GNU Findutils manual, it says

If the expression contains no actions other than ‘-prune’, ‘-print’ is performed on all files for which the entire expression is true.

Apparently, debian's manual is wrong, because it's just a GNU Find. And I have no idea why this happened, since it's just a copy to me.

Francis
  • 737
  • 1
  • 7
  • 21