Chmod and -r +r

I have tried calling the command chmod in the wrong order. chmod file.txt -r This worked for some reason. chmod file.txt +r On the other hand refused to work. Why is this? For what reason does one command work, and the other not?


This is a quirk of how GNU chmod handles input, and is not portable to all POSIX-compatible chmod implementations.

Note that the POSIX chmod coomand-line syntax requires mode to come first, as does GNU chmod (options should come before mode, too). Anything else is an undocumented implementation quirk.


Now, onto why it happens in this particular implementation:

It's hinted at in the manual:

Typically, though, ‘chmod a-w file’ is preferable, and chmod -w file (without the --) complains if it behaves differently from what ‘chmod a-w file’ would do.

Briefly, options parsed by getopt are prefixed with a -. Like in ls -a, a is an option. The long form ls --all has all as an option. rm -rf (equivalent to rm -r -f) has both r and f options.

Everything else is a non-option argument, technically called operands. I like to call these positional arguments, as their meaning is determined by their relative position. In chmod, the first positional argument is the mode and the second positional argument is the file name.

Optimally, mode should not lead with a -. If it does, you should use -- to force parsing as an operand instead of an option (i.e. use chmod a-w file or chmod -- -w file instead of chmod -w file. This is also suggested by POSIX.


If you look at the source code, you'll notice it uses getopt to parse command-line options. Here, there's special handling for 'incorrect' modes like -w:

    case 'r':
    case 'w':
    case 'x':
    case 'X':
    case 's':
    case 't':
    case 'u':
    case 'g':
    case 'o':
    case 'a':
    case ',':
    case '+':
    case '=':
    case '0': case '1': case '2': case '3':
    case '4': case '5': case '6': case '7':
      /* Support nonportable uses like "chmod -w", but diagnose
         surprises due to umask confusion.  Even though "--", "--r",
         etc., are valid modes, there is no "case '-'" here since
         getopt_long reserves leading "--" for long options.  */

Taking your example:

  • chmod a-r file.txt would be the most robust invocation.
  • chmod +r file.txt works because the first argument is positionally interpreted as the mode.
  • chmod -r file.txt still works because the -r is interpreted as a short r option and special-cased.
  • chmod -- -r file.txt is correct and works because the -r is positionally interpreted as the mode. This differs from the case without -- because with -- the -r is not interpreted as an option.
  • chmod file.txt -r still works because the -r is interpreted as a short r option and special-cased. Options are not position-dependent. This technically abuses an undocumented quirk.
  • chmod file.txt +r does not work because the +r is a operand, not an option. The first operand (file.txt) is interpreted as a mode ... and fails to parse.