Solution 1:

chown -R admin $(ls -I content)

ls -I will list all except specified pattern.

Solution 2:

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.