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
$
#./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
Dependency Failure While Installing Libboost-All-Dev on Ubuntu Core 14.04
X11 Forwarding of Gui App in Docker Container
How Syscall Knows Where to Jump
How to Find Files Containing a String Using Egrep
How to Execute an Arbitrary Script with a Working Directory of The Directory Its In
Passing an Array as Command Line Argument for Linux Kernel Module
Tar Command Changing The Owner:Group While Extracting
How to Suppress Warnings in Qt Creator
How to Set Folder Permissions for a Particular Container on Elastic Beanstalk
Check The Output of "Make" and Exit Bash Script If It Fails
Create Infinite Looping Repeating File Cat in Linux/Bash
How to Package Mono Applications for Debian/Ubuntu
How to Set CPU Load on a Red Hat Linux Box
Exploiting a String-Based Overflow on X86-64 with Nx (Dep) and Aslr Enabled
Update Yum Package Using Localinstall
Svn Checkout Fails with "Chunk Delimiter Was Invalid" - What Can Be Done
When Will Send() Return Less Than The Length Argument
How to Store Your Github Https Password on Linux in a Terminal Keychain