In Linux the most general tool to do something to files meeting some criteria is find
. Few other answers base on the find … | xargs …
idea, which is robust only if it uses null-terminated strings. This means find … -print0 | xargs -0 …
, but these options are not required by POSIX.
With POSIX find
it's usually better to use -exec
than to pipe to xargs
. Personally I prefer -exec
even if I can safely use xargs
. Knowing that -exec
is also a test, so it can be used to build custom tests (example), it's good to be familiar with it anyway; and then there is no reason not to use it instead of xargs
. The real power of xargs
is its ability to parse strings with quotes and escaped characters; but this is hardly ever useful when reading from find
, almost always it's harmful. Non-POSIX options like -0
and --no-run-if-empty
can make xargs
a good companion of find … -print0
, but still the good POSIX -exec
is (almost?) always at least as good.
Your problem can be solved by
find /home/admin/web/public_html \
-path /home/admin/web/public_html/content -prune \
-o -exec chown admin {} +
It works like this: if the path is …/content
then do not descend into it (-prune
); otherwise (-o
) execute chown admin
on the file (note: directory is also a file).
Notes:
Do not use chown -R
here. The first file tested is /home/admin/web/public_html
and if you use chown -R
on it then nothing will be excluded.
-exec chown … {} +
can and will pass multiple paths to chown
, while -exec chown … {} \;
would pass just one (so there would be one chown
spawned per file). The syntax with +
reduces the number of spawned chown
processes, this speeds things up. Even then find
will spawn more than one chown
process if the number of files is too large for a single command line (compare "argument list too long"). Note it works because chown
can take multiple paths; some tools cannot and for them the syntax with +
is out of the question.
-path
matches against the entire path. The path is what find
thinks the path is, not necessarily the canonical path. If the starting path is /home/admin/web/public_html
then every path tested will start with this string; but if the starting path is ./
then every path tested will start with this string. In the former case -path /home/admin/web/public_html/content
will never be true, even if realpath ./
prints /home/admin/web/public_html
, because the relevant directory will be identified by the string ./content
and this is the string you would want to match with -path
. In general you need to adjust the argument of -path
to the starting location(s) (or use wildcards maybe).
If you need to exclude multiple patterns then follow this example:
find . \
-path ./content -prune \
-o -path ./foo/bar -prune \
-o -path '*/baz' -prune \
-o -exec chown admin {} +
which can be compacted to
find . \
\( -path ./content \
-o -path ./foo/bar \
-o -path '*/baz' \
\) -prune \
-o -exec chown admin {} +
where parentheses are important.
With -regex
you may be able to combine multiple patterns into one (example). This test is not required by POSIX though.
Other tests (e.g. -name
or even -exec
) can be used to exclude files.