30

In a shell script, how do I easily and non-invasively test for write access to a file without actually attempting to modify the file?

I could parse the output of stat, but that seems really complex, and perhaps brittle, though I'm not sure how much stat output differs across implementations and time.

I could append to the end of the file and see if that succeeds, but that's potentially dangerous, for two reasons I can think of:

  1. I now have to remove the addition, and in case some other process writes to the file, this immediately becomes non-trivial as my line is no longer the last one.
  2. Any process reading the file may have arbitrary requirements on the contents of that file, and I may just have broken that application.
user50849
  • 5,202

3 Answers3

34

Just use the -w flag of the test utillity:

[ -w /path/to/file ] && echo "writeable" || echo "write permission denied"

Note that if you're going to write to the file later, it's still possible that you won't be able to write to it. The file may have moved, the permissions may have changed, etc. It can also happen that -w detects write permissions but some other factor intervenes to make the file not writable.

chaos
  • 48,171
14

Another approach:

if >> /path/to/file
then
    echo "writeable"
else
    echo "write permission denied"
fi

This will attempt to open the file for appending, and, if that succeeds, run no command (i.e., run a null command) with output to the file. 

Beware that this creates an empty file if it didn't exist.

The -w operator of the test command might just do a stat and then try to figure out whether it looks like you should have access.  My alternative (above) is more reliable than the test approach in some special conditions, because it forces the access check to be done by the kernel rather than the shell.  For example,

  • if the file is on a non-Unix file system – especially if it is remotely mounted from a non-Unix file server – because stat might return a mode value that is misleading.
  • if the file is on a file system that is mounted read-only.
  • if the file has an ACL, and the mode makes it look like you should have access, but the ACL denies it, or vice versa.
  • if some security framework (AppArmor, SELinux, …) denies access to the file.
  • The touch command could be used instead of redirection, I think. – muru Oct 06 '14 at 17:18
  • 1
    This modifies both the last access, and the last modified dates, though. Doesn't it? – o0'. Oct 06 '14 at 17:26
  • 4
    I just tested this (on Debian). Neither time was altered, and that is the way it should work on any Unix. Access time should be updated only if you read from the file, modify time should be updated only if you write to the file. This code does neither. @Schwern: do you have a reference for your statement? Have any of you tried it? – G-Man Says 'Reinstate Monica' Oct 06 '14 at 18:21
  • 3
    P.S. @muru: I just tried to touch a file that I owned but didn't have write access to, and it succeeded. I guess it chmods the file and chmods it back. So touch appears to be absolutely useless as an answer to the question. – G-Man Says 'Reinstate Monica' Oct 06 '14 at 18:24
  • 2
    @G-Man ah, that's interesting. IIRC vim has that behaviour of quickly changing the permissions when forced to write on read-only files. I checked with strace, touch's open fails with EACCES, but the subsequent call to utimensat succeeds, which is why I think touch on the whole exits successfully. – muru Oct 06 '14 at 18:31
  • 2
    @muru: Thanks for checking that. utimensat(2) says, “Permissions requirements: 1. write access (or) 2. the caller’s effective user ID must match the owner of the file, ….” – G-Man Says 'Reinstate Monica' Oct 06 '14 at 18:38
  • 1
    FYI: This only works on files, not directories, while [ -w works with both files and directories. Neither solutions reliably work to test if a file is writable if-and-if-not exists. Perhaps the OP's implementation is incorrect: just run the write command and catch the error rather than test ahead of time. – zamnuts Oct 06 '14 at 18:49
  • 1
    It shouldn't be necessary, but instead of using sleep 0 in your second example you can use the : command. It's the standard shell built-in "do nothing" command. – Ross Ridge Oct 06 '14 at 21:21
  • @G-Man You're right, I stand corrected. – Schwern Oct 07 '14 at 17:24
  • 1
    Most implementations will use at least access(2), so the kernel would do the check. The only problem with access is that it uses the real user id instead of effective user id, but modern systems have the POSIX faccessat(..., AT_EACCESS) that can be used instead (or eaccess or other system call). – Stéphane Chazelas Dec 01 '17 at 18:45
  • 4
    Other problems like for Champignac's: >> file is not portable (for instance, runs the NULLCMD in zsh), use true >> file instead. And if the file is a named pipes, it has nasty side effects. – Stéphane Chazelas Dec 01 '17 at 19:04
4

G-man is right: [ -w ] won't always tell the truth. Here to cope with non-existing file and Permission denied message from shell:

( [ -e /path/to/file ] && >> /path/to/file ) 2> /dev/null && 
  echo writable || 
  echo not writable

Update: Looks frightening, isn't? Well, it is. Hmm... how to phrase it... DON'T USE THIS, unless you perfectly know you are in the conditions it requests to work as expected. See Stephane's comment.

What to conclude, then? Even if [ -w ] does not tell the truth, it is the one command that is intended to do the job. If it does not, well, we'll blame it, write bug reports, and it will work in the future. Better check the conditions under which it works and use [ -w ]; write special code for special cases. Workarounds have their own conditions.

[ -w /path/to/file ]

is the best a priori.

  • 6
    If the file is a named pipe, that will hang if there's no read, and even if there's a reader will have nasty side effects. test -w in most implementations uses access(2) so should be enough to test permissions. – Stéphane Chazelas Dec 01 '17 at 18:34