I accidentally chmod -R +x on a directory. How do I restore the correct permissions?

Well, to be specific, it was chmod -R 755. Now every file is executable, which I don't want. I am thinking that I should look at the first two bytes of each file for the #!, but will this cover everything? Should I instead use file to look at everything and base my decision on that? Or, more likely, is there an even better way to do this?

What is the preferred way to recursively go through a directory and set -x on files that are not 'supposed to be' executable?

• Did you do this at / or another directory? – gvkv Aug 27 '10 at 19:09
• @gvkv: Not /, a directory completely owned by me. – Larry Wang Aug 27 '10 at 19:12
• @Larry in the future you should probably use some variant of +x rather than raping all permissions, and possibly causing writes on all files. – xenoterracide Aug 27 '10 at 20:38
• @xenoterracide: Agreed. What I really wanted was to give group same permissions as me (which did end up happening!), I just didn't do enough thinking before I typed. – Larry Wang Aug 27 '10 at 20:48
• How many files are we talking about? How many should be executable? Is there any way to tell from the file names? – David Thornley Aug 27 '10 at 21:48

There's no magic bullet here. The permissions carry information which is not always redundant.

If you'd done this in a system directory, your system would be in a very bad state, because you'd have to worry about setuid and setgid bits, and about files that are not supposed to be world-readable, and about files that are supposed to be group- or world-writable.

As for executability, a good rule of thumb would be to make everything that doesn't look like it could be executed, be nonexecutable. The kernel can execute scripts whose first two bytes are #!, ELF binaries whose first four bytes are \x7fELF where \x7f is the byte with the value 12, and a few rarer file types (a.out, anything registered with binfmt_misc). Hence the following command should restore your permissions to a reasonable state (assumes bash 4 or zsh, otherwise use find to traverse the directory tree; warning, typed directly into the browser):

for x in **/*; do
if ! [ -f "$x" ]; then continue; fi # skip all but regular files case$(head -c 4 "$x") in "#!"??) :;; # skip script "\x7fELF") :;; # skip ELF executable *) chmod a-x "$x";;
esac
done


Note that there is a simple way to back up and restore permissions of a directory tree, on Linux and possibly other unices with ACL support:

getfacl -R >saved-permissions
setfacl --restore=saved-permissions

• Thanks! Fortunately, I think everything falls into these two categories. If this misses anything, I can deal with it later. – Larry Wang Aug 27 '10 at 21:35
• Bear in mind that **/* requires globstar. – Chris Down Nov 7 '11 at 14:03
• I'd recommend two changes to this script. One, use find rather than globstar; two, instead of looking at the head, use the file command to see what it is and branch from there. – Shadur Nov 12 '11 at 15:52
• @Shadur find is less reliable than globstar, globstar is preferable in almost every case. – Chris Down Nov 15 '11 at 21:58
• @Gilles Preferable as in "if you have it, it's far superior", not only is it faster, it's also more reliable (and doesn't have unexpected SNAFUs). – Chris Down Nov 15 '11 at 22:11

I believe you will want something like

find dir -type f -exec chmod ugo-x '{}' +

This looks for all regular files, recursively in dir, (it excludes directories, and devices) and removes the executable bit.

I would start here, and then work my way towards making files that are supposed to be executable, executable.

The following should work exactly as you asked for ( it will find all regular files, grep them for #! and then remove the x bits if not found )

find . -type f | xargs grep -L #! | xargs chmod ugo-x


possibly a better version of the above (less pipes)

find . -type f -exec grep -L #! '{}' + | xargs chmod ugo-x

• Make that grep -L '^#!' at least (the quotes are necessary, and ^ restricts to matching at the beginning of the line), but it's still too permissive as it matches #! on any line. Using xargs will fail with file names containing spaces or quoting characters; use xargs -d '\n' (requires GNU xargs). – Gilles Aug 27 '10 at 21:30

Well, without a shebang line, the file will be executed as a shell script, nominally with /bin/sh. You're idea is a good start and on the assumption that the directory in question doesn't contain mission-critical files there probably isn't much risk to executing some grep and chmod combo. You may encounter false positives, i.e., files with a shebang line that are not meant to have their executable bit set but without knowing more information on the purpose of what's in the directory, only you can decide whether that poses a significant existential threat to your system and/or data.

• I'm not worried so much about false positives as false negatives. There are binaries that I don't think start with #!. – Larry Wang Aug 27 '10 at 19:48