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, andchmod -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 shortr
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 shortr
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.