Gnu Find: When Does The Default Action Apply

GNU find: when does the default action apply?

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]

How does make app know default target to build if no target is specified?

By default, it begins by processing the first target that does not begin with a . aka the default goal; to do that, it may have to process other targets - specifically, ones the first target depends on.

The GNU Make Manual covers all this stuff, and is a surprisingly easy and informative read.

Why is my `find` command giving me errors relating to ignored directories?

why is the find searching in the .git directory?

GNU find is clever and supports several optimizations over a naive implementation:

  • It can flip the order of -size +512b -name '*.txt' and check the name first, because querying the size will require a second syscall.
  • It can count the hard links of a directory to determine the number of subdirectories, and when it's seen all it no longers needs to check them for -type d or for recursing.
  • It can even rewrite (-B -or -C) -and -A so that if the checks are equally costly and free of side effects, the -A will be evaluated first, hoping to reject the file after 1 test instead of 2.

However, it is not yet clever enough to realize that -not -path '*/.git/*' means that if you find a directory .git then you don't even need to recurse into it because all files inside will fail to match.

Instead, it dutifully recurses, finds each file and matches it against the pattern as if it was a black box.

To explicitly tell it to skip a directory entirely, you can instead use -prune. See How to exclude a directory in find . command

Makefile: all vs default targets

You can call them shirley if you like; neither of the labels you mention has any special semantics. The default behavior of make is to run the first target in the Makefile if you don't specify a target as a command-line argument. If you like to override this behavior, there is the .DEFAULT: special target.

There is a convention to have a target named all which builds everything, but this is just human convention, not a special case or a requirement as far as Make is concerned.

Similarly, there is a (weak) convention to call the default target default, but similarly, this is just a human label (and somewhat overlaps and possibly conflicts with the all convention).

So the following Makefile does exactly the same thing:

.PHONY: shirley all default
default: hello
all: hello
shirley: hello

hello: hello.cpp
# (Make already knows how to build an executable out of a .cpp file)

You can omit any or all of the phony targets above, and the only difference will be that humans won't be able to say make shirley when they (effectively) mean make hello.

Bottom line: Construct your Makefile so that make does what a reasonable end-user expects without reading too much README files or inspecting the Makefile. Often that will be make all (and you should probably have a target with this name, just to satisfy human conventions, even if it's not the default) but this obviously depends on your project and your users' expectations.


Don't call me Shirley.

Order of evaluation in find

Explanation

The various implementations of find tend to call the operands different names in the man page. However the general execution of any find implementation that follows the posix standard is going to be the same.

find [-H | -L] path ... [operand_expression ...]

man page quotes taken from posix find man page

Where each set of two touching expressions (meaning -operand (Argument)), that does not have explicit operator separating the expressions, has an implicit -a (AND) operator separating them.

expression  [-a]  expression
Conjunction of primaries; the AND operator is implied by the juxtaposition of
two primaries or made explicit by the optional -a operator.
The second expression shall not be evaluated if the first expression is false.

Conclusion

So any implementation that follows the posix standard must execute expressions left to right; and

find . -type d -mount -ctime +5 -prune -exec 'rm {}' \;

is equivalent to

find . -type d  -a -mount -a -ctime +5 -a -prune -a -exec 'rm {}' \; 

Meaning that -exec will only be executed if all preceding operands are true.

Other

It's also worth noting that while -exec may not be called an explicit action by your find implementation, it should still get treated in a similar manner (namely replacing the -print action)

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

Edit

Technically true that not every implementation has to be posix complaint. He're the almost identical quote from solaris find man page

expression [-a] expression
Concatenation of primaries (the and operation is implied by the juxtaposition of two primaries).

awk: default action if no pattern was matched?

You could invert the match using the negation operator ! so something like:

!/pattern 1|pattern 2|pattern/{default action}

But that's pretty nasty for n>2. Alternatively you could use a flag:

{f=0}
/pattern 1/ {action 1;f=1}
/pattern 2/ {action 2;f=1}
...
/pattern n/ {action n;f=1}
f==0{default action}

How can I make the find Command on OS X default to the current directory?

If you can't discipline yourself to use find 'correctly', then why not install GNU find (from findutils) in a directory on your PATH ahead of the system find command.

I used to have my own private variant of cp that would copy files to the current directory if the last item in the list was not a directory. I kept that in my personal bin directory for many years - but eventually removed it because I no longer used the functionality. (My 'cp.sh' was written in 1987 and edited twice, in 1990 and 1997, as part of changes to version control system notations. I think I removed it around 1998. The primary problem with the script is that cp file1 file2 is ambiguous between copying a file over another and copying two files to the current directory.)

Consider writing your own wrapper to find:

#!/bin/sh
[ ! -d "$1" ] && set -- . "$@"
exec /usr/bin/find "$@"

The second line says "if argument 1 is not a directory, then adjust the command line arguments to include dot ahead of the rest of the command. That will be confusing if you ever type:

~/bin/find /non-existent/directory -name '*.plist' -print

because the non-existent directory isn't a directory and the script will add dot to the command line -- the sort of reason that I stopped using my private cp command.



Related Topics



Leave a reply



Submit