20

How can I reset permission to default according to the mask, so they will have permissions set as the file was just created

Example of what I want to achieve: umask is set to 0022 so

touch file
mkdir directory

file's permissions now are rw-r--r--

directory's permissons now are rwxr-xr-x

chmod 777 file
chmod 777 directory

file's permissions now are rwxrwxrwx

directory's permissions now are rwxrwxrwx

is there a way to reset perms back to default so file will be rw-r--r-- and directory rwxr-xr-x using chmod?

muru
  • 72,889
lllook
  • 410

4 Answers4

20

Using subtraction is only correct for some umask values - it's better to use mode & ~umask, like in PSkocik's answer, or chmod =rw (or chmod =rwx for directories) which applies the umask as specified by the standard, as noted by ilkkachu in the comments.


In some cases, you can just subtract the umask from 0666 for files and 0777 for directories to get default permissions:

$ printf "%04d\n" "$((0777 - $(umask)))"
0755
$ printf "%04d\n" "$((0666 - $(umask)))"
0644

Accordingly, you can apply chmod:

chmod $((0666 - $(umask))) file
chmod $((0777 - $(umask))) directory

In bash, you have to use printf to force the output in octal:

$ printf "%04d\n" "$((0777 - $(umask)))"
0493
$ printf "%04o\n" "$((0777 - $(umask)))"
0755

Another way would be to create a new file and directory, and use them as reference:

touch file2
mkdir directory2 
chmod --reference=directory2 directory
chmod --reference=file2 file
muru
  • 72,889
  • 3
    also I found something like chmod =rwx file, not sure what exactly it does but looks like it resets permissions to default too but only for directories – lllook Dec 16 '15 at 23:08
  • 1
    Note that Bash, unlike ZSH, treats values beginning with 0 as octal values. For these examples to work in Bash, the numbers need to be prefixed with 10#. For example: chmod $((10#0666 - 10#$(umask))) file – John Karahalis Jan 05 '21 at 20:46
  • The produces incorrect results for files if the umask has “1” anywhere (uncommon perhaps but still possible). umask isn’t subtracted. – Stephen Kitt May 25 '21 at 10:59
  • @JohnKarahalis, as long as there's no carry, it doesn't matter if the numbers are taken as octal or decimal. E.g. 6 - 4 = 2 in either. 4 - 6 would differ, though. But like Stephen says, the umask shouldn't be subtracted but bitwise modified. So we need the numbers to be interpreted correctly. In Bash, Dash, and Ksh, printf "%o\n" "$(( 0666 & ~$(umask) ))" should work. Zsh needs printf "%o\n" "$(( 8#0666 & ~8#$(umask) ))", but then that doesn't work in Dash. – ilkkachu May 25 '21 at 14:49
  • 2
    chmod =rwx file also works, it sets the permissions to 0777 modified by the umask. See the sentences with "if who is not specified" in https://pubs.opengroup.org/onlinepubs/9699919799.2018edition/utilities/chmod.html – ilkkachu May 25 '21 at 14:52
  • @ilkkachu Yes, that's true if the umask is being bitwise modified. To be pedantic, though, even though 6 - 4 is the same in octal and decimal, as you note, 666 - 444 is not. Thus, $((0666 - 0444)) in Bash is equal to decimal 146 but $((10#0666 - 10#0444)) is equal to decimal 222. In any case, you may be right that bitwise is the way to go to avoid confusion. – John Karahalis May 28 '21 at 03:00
  • @JohnKarahalis, well, it is in the sense that 666o - 444o = 222o, when all three numbers are in octal. Somehow, I first thought Zsh did just that, output in octal if the inputs are in octal, but of course it doesn't. But bitwise masking isn't just to avoid confusion, it's to deal with a case like 0600 masked with 0002. If you do the subtraction, you get 576 (octal to octal), or 598 (decimal to decimal). Neither is right, it should get left to just 0600. – ilkkachu May 28 '21 at 11:24
9

The masks are applied via bitwise AND with the bitwise negated mask so if you want to create your own final permission mode, you can do:

$((mode & ~umask))

You'll need to print that back in octal so that you can pass it to chmod:

$ chmod `printf '%o' $((0777 & ~$(umask)))` directory
$ chmod `printf '%o' $((0777 & ~0111 & ~$(umask)))` file
#^additional implicit mask of 0111 for files

where 0777 is the permission mode you want to apply the mask to (you can obtain it with stat -c %a file or stat -c %a directory).

You can echo the above to see what the process substitution will evaluate to (for an umask of 0022, you will get 755 and 644).

You could make a generic function out of it:

#takes a umask as first param and applies it to each folowing param (files)
maskMode(){
  local mask="$1" dmask mode a
  dmask="$((mask & ~0111))"; shift
  for a; do
    mode=0`stat -c "%a" "$a"`
    chmod `printf "%0.4o\n" $(($mode & ~mask))` "$a"
  done
}

For your particular use, chmod reference files are another option.

Petr Skocik
  • 28,816
3

If you want to do it recursively through the current directory -

chmod -R $((777 - `umask`)) .
chmod -R -x+X .

You can replace `umask` with $(umask).

nomad
  • 401
2

Recursive files and directories

For anyone looking to reset all files and directories recursively within a parent directory:

sudo find . -type f -print0 | xargs -0 sudo chmod $((0666 - $(umask)))
sudo find . -type d -print0 | xargs -0 sudo chmod $((0777 - $(umask)))

Explanation

The other answers showed how to reset individual files and directories (or apply one rule to both recursively) with chmod -R $((0666 - $(umask))) and chmod -R $((0777 - $(umask))) respectively. (Great answers, thanks!)

I then found this answer showing how to apply permissions selectively using find throughout the directory:

find . -type f -print0 | xargs -0 chmod 644

This will change files recursively, and therefore

find . -type d -print0 | xargs -0 chmod 755

will apply to directories.

Stringing these answers together provides the above solution. Note that this requires the GNU version of find and xargs for the -print0/-0 options, respectively.

Hope it helps others!

AdminBee
  • 22,803
arneyjfs
  • 121