12

I have a script I'm running to fix file ownership and permissions after an rsync. Questions of the optimal way to do my task aside, I wonder, is there a way to run chmod and chown at the same time?

In the current iteration of my script, I'm finding files twice.

find /var/www/mysite -exec chown  www-data:www-data {} \;
find /var/www/mysite -type f -exec chmod 775 {} \;

I thought it would be nice if I could change both the permissions and owner/group with a single command. After some googling, I was surprised to learn that such a command, argument, or option doesn't exist.

Can I change both the permissions and ownership at the same time, to avoid finding each file twice?

Edit A community edit or post or something suggested that this question is a duplicate of "Change all folder permissions with 1 command". This question is different because it asks about changing both permissions and ownership at the same time, not just permissions.

user394
  • 14,194
  • 21
  • 66
  • 93
  • 7
    you really, really, really don't want all your web files to be 775. – Olivier Dulac Apr 16 '20 at 14:07
  • If your files have permission 755 be aware that a user on the same server (for instance shared hosting) can edit your files. It is better to use 755 for folder and 644 for files. – GuyT Apr 16 '20 at 15:57
  • 1
    @GuyT: That is incorrect. `755` is `rwxr-xr-x`. It doesn't allow modification by anyone other than the owner. – R.. GitHub STOP HELPING ICE Apr 16 '20 at 17:14
  • @R..GitHubSTOPHELPINGICE I am sorry, you are correct. – GuyT Apr 16 '20 at 17:28
  • 1
    @OlivierDulac this is for a local development instance inside a vbox vm – user394 Apr 16 '20 at 17:47
  • Given that your web files are almost certainly interpreted by some other program (php, ruby, python, perl files, etc.), they don't even need execute permission. – Alexander Apr 17 '20 at 00:24
  • 1
    Does this answer your question? [Change all folder permissions with 1 command](https://unix.stackexchange.com/questions/349053/change-all-folder-permissions-with-1-command) – G-Man Says 'Reinstate Monica' Apr 18 '20 at 05:34
  • Add `-print0` to the end and pipe the filenames into a single instance of **Perl** which can do the the `chmod` and `chown` as simple library calls without starting new processes. – Mark Setchell Apr 18 '20 at 20:14
  • @G-ManSays'ReinstateMonica' No it doesn't -- see my edit at the bottom. – user394 Apr 18 '20 at 21:31
  • Yeah, but did you look at my *answer* to that other question?  It shows how to run two commands on a directory tree with a single invocation of `find` — which is what you’re asking for. (OK, not exactly the same, but it’s more advanced than jesse_b’s answer to your question, so you should have been able to adapt it.) – G-Man Says 'Reinstate Monica' Apr 18 '20 at 21:43
  • @G-ManSays'ReinstateMonica' feel free to post the adapter answer, that fulfills the requirements of this question, as an answer on this question. – user394 Apr 18 '20 at 22:11
  • It would be too similar to jesse_b’s answer to be worth posting as a separate answer. – G-Man Says 'Reinstate Monica' Apr 18 '20 at 22:13

4 Answers4

18

You can pass multiple exec commands:

find /var/www/mysite -exec chown www-data:www-data {} \; \
     -type f -exec chmod 775 {} \;
Kusalananda
  • 320,670
  • 36
  • 633
  • 936
jesse_b
  • 35,934
  • 12
  • 91
  • 140
  • 8
    @user394 No, you can't, because each predicate (`-type`, `-exec` etc) is a _test_, and there's a logical AND between them (implicitly). You could work out the logic with OR (`-o`) and parentheses. Possibly something like `\( -type d -exec chmod g+s {} \; \) -o \( -type f -exec chmod 775 {} \; \)` etc. – Kusalananda Apr 15 '20 at 19:17
  • 8
    @user394 In Jesse's code, `chmod` will only ever be executed for regular files (`-type f`) that the preceding `chown` was successful for. If the `chown` _fails_, the `-type f` etc. won't happen, and the next thing found will be considered instead. (that's how `-exec` can be seen as a test). – Kusalananda Apr 15 '20 at 19:19
10

You could add the equivalent options to your rsync command:

rsync <your_options> --chown www-data:www-data --chmod=F775 <source> <destination>

You can use prefix F in --chmod for files and D for directories.

Freddy
  • 25,172
  • 1
  • 21
  • 60
  • I'm guessing the user UID database is not the same between both hosts. This would be one reason for using ldap or another central repository of UID info on a network. – Criggie Apr 16 '20 at 02:34
3

For completeness xargs can do all sorts of interesting things in pipelines too.

find . -type f -print0 | xargs -0 -I VAR -- sh -c 'chmod 775 "VAR" && chown www-data:www-data "VAR" '

This produces a stream of filenames (not directory names) with nulls for separators, so deals with spaces in filenames.

-0 tells xargs to separate inputs on the null.
-I VAR says to use VAR as the "variable name" rather than {}
-- and everything after it is what to run for each line

This may be more readable, but it will be starting a new shell for each run of the double-barrelled command.

Criggie
  • 1,751
  • 12
  • 23
  • Never embed the text substituted by `xargs` inside the shell code (or code to any interpreter for that matters), as that makes it a code injection vulnerability. Use `-exec sh -c 'for file do chmod 775 "$file" && chown www-data: "$file"; done' sh {} +`. `xargs` is rarely useful to process `find`'s output. – Stéphane Chazelas Apr 16 '20 at 14:33
2

If you're worried about finding each file twice, you may also want to worry about the number of forks that running a combination of commands via xargs would produce.

People often forget that perl has equivalents for many of these basic shell commands. Here's one way I'd do this (tested):

find | perl -lne 'chown 1001,1001, $_; -d $_ ? chmod(0755, $_) : chmod(0644, $_)'

You'd have to use the numeric uid, gid, and mode, but think about this: this does not fork at all (beyond the one find and the one perl).

And you don't need to know a lot of perl to grok it either.

The chown and chmod are clear enough. -d is the same as in shell's [ or the test command, and the ternary operator foo ? bar : baz is not unique to perl. (As a bonus, you get to use different modes for directories and files.)

In fact it's the options that may need explanation if you're new to perl. A bit simplified:

  • the -n means "run this once for each line of STDIN, setting $_ to the line"
  • -l makes the line feed at the end of each line disappear (because otherwise that becomes part of $_)
  • -e is familiar to people who know sed; it says this is the expression to evaluate/run.